Updated with goodies.
1.1 Binary file downloads/cau.zip has changed
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2.2 +++ b/downloads/sml.py Mon Jul 18 01:51:06 2011 +0200
2.3 @@ -0,0 +1,447 @@
2.4 +"""
2.5 +Name: sml - Simple Modal Logic Lib.
2.6 +Description: sml provides classes for simple modal logic formulae construction and reduction. It also features a disfunctional parser for the SML syntax.
2.7 +Author: Eugen Sawin <sawine@informatik.uni-freiburg.de>
2.8 +"""
2.9 +
2.10 +TMP_DIMACS = "dimacs~"
2.11 +TMP_SOLUTION = "solution~"
2.12 +
2.13 +def main():
2.14 + # edit your formulae here
2.15 + p = Variable("p")
2.16 + q = Variable("q")
2.17 + r = Variable("r")
2.18 + s = Variable("s")
2.19 + formula1 = Imp(Eq(p, q), r)
2.20 + formula2 = Not(Or(p, q))
2.21 + formula3 = Not(Not(p))
2.22 + formula4 = Imp(Box(Imp(Diamond(p), p)), Box(Imp(p, Box(p))))
2.23 + formula5 = Imp(And(Or(p, q), r), Not(s))
2.24 + formula6 = And(Or(p, Or(q, Not(s))), And(Or(r, Not(s)), Or(Not(p), s)))
2.25 + formula = formula6 # choose your formula here
2.26 +
2.27 + args = parse_arguments()
2.28 + if (args.formula):
2.29 + scanner = Scanner(TOKENS)
2.30 + parser = Parser(SYNTAX, scanner)
2.31 + (accepted, out, tree_nodes) = parser.parse(args.formula)
2.32 + if not accepted:
2.33 + print EXCUSE
2.34 + return
2.35 + tree = SyntaxTree(out, tree_nodes)
2.36 + formula = tree.root
2.37 +
2.38 + original = formula
2.39 + print "formula: \t\t\t%s [%i]" % (formula, formula.depth())
2.40 + formula = expand_imp_eq(formula)
2.41 + print "expanded -> and <->: \t\t%s [%i]" % (formula, formula.depth())
2.42 + formula = de_morgan(formula)
2.43 + print "reduced ~: \t\t\t%s [%i]" % (formula, formula.depth())
2.44 + formula = reduce_iter(formula)
2.45 + print "reduced iterated modalities: \t%s [%i]" % (formula, formula.depth())
2.46 + formula = reduce_iter(dist_mod((formula)))
2.47 + print "distributed modalities+: \t%s [%i]" % (formula, formula.depth())
2.48 + formula = mcnf(formula)
2.49 + print "alpha mcnf: \t\t\t%s [%i]" % (formula, formula.depth())
2.50 + #cnf_formula = cnf(original)
2.51 + #print "alpha cnf: \t\t\t%s [%i]" % (cnf_formula, cnf_formula.depth())
2.52 +
2.53 + if not args.dimacs:
2.54 + dimacs_file = TMP_DIMACS
2.55 + else:
2.56 + dimacs_file = args.dimacs
2.57 + solution = sat_test(original, dimacs_file)
2.58 + if solution:
2.59 + print "satisfiable: \t\t\t%s " % (", ".join([str(s) for s in solution]))
2.60 + else:
2.61 + print "not satisfiable"
2.62 +
2.63 +from subprocess import call, PIPE
2.64 +
2.65 +def sat_test(formula, dimacs_file):
2.66 + out, variables = dimacs(formula)
2.67 + with open(dimacs_file, "w") as f:
2.68 + f.write(out)
2.69 + call(["minisat2", "-verb=0", dimacs_file, TMP_SOLUTION], stdout=PIPE)
2.70 + with open(TMP_SOLUTION) as f:
2.71 + solved, assignment = f.readlines()
2.72 + solved = solved[0] == "S"
2.73 + if solved:
2.74 + positive = set()
2.75 + for a in assignment.split():
2.76 + neg = a[0] == "-"
2.77 + if not neg:
2.78 + positive.add(int(a))
2.79 + solution = []
2.80 + for v, n in variables.iteritems():
2.81 + if n in positive:
2.82 + solution.append(Variable(v))
2.83 + else:
2.84 + solution.append(Not(Variable(v)))
2.85 + return solution
2.86 +
2.87 +def dimacs(formula):
2.88 + def clausify(f):
2.89 + if f.id == AND:
2.90 + c1 = clausify(f.values[0])
2.91 + c2 = clausify(f.values[1])
2.92 + if c1:
2.93 + clauses.append(clausify(f.values[0]))
2.94 + if c2:
2.95 + clauses.append(clausify(f.values[1]))
2.96 + if f.id == OR:
2.97 + clause = []
2.98 + clause.extend(clausify(f.values[0]))
2.99 + clause.extend(clausify(f.values[1]))
2.100 + return clause
2.101 + if f.id == NOT:
2.102 + return ["-" + clausify(f.values[0])]
2.103 + if f.id == VARIABLE:
2.104 + if f.values[0] in variables:
2.105 + n = variables[f.values[0]]
2.106 + else:
2.107 + n = len(variables) + 1
2.108 + variables[f.values[0]] = n
2.109 + return str(n)
2.110 +
2.111 + variables = {}
2.112 + clauses = []
2.113 + clausify(formula)
2.114 + out = "c DIMACS CNF input file generated by sml\n"
2.115 + out += "c Author: Eugen Sawin <sawine@informatik.uni-freiburg.de>\n"
2.116 + out += "p cnf %i %i\n" % (len(variables), len(clauses))
2.117 + out += " 0\n".join([" ".join(c) for c in clauses]) + " 0\n"
2.118 + return out, variables
2.119 +
2.120 +def values(fun, f):
2.121 + return [fun(v) for v in f.values]
2.122 +
2.123 +def expand_imp_eq(formula):
2.124 + expmap = {IMP: lambda (a, b): Or(Not(a), b),
2.125 + EQ: lambda (a, b): And(Or(Not(a), b), Or(a, Not(b)))}
2.126 + if not isinstance(formula, Operator):
2.127 + return formula
2.128 + if formula.id in expmap:
2.129 + return expmap[formula.id](values(expand_imp_eq, formula))
2.130 + return formula.__class__(*values(expand_imp_eq, formula))
2.131 +
2.132 +def de_morgan(formula):
2.133 + negmap = {AND: lambda (a, b): Or(negate(a), negate(b)),
2.134 + OR: lambda (a, b): And(negate(a), negate(b)),
2.135 + BOX: lambda (a,): Diamond(negate(a)),
2.136 + DIAMOND: lambda (a,): Box(negate(a)),
2.137 + VARIABLE: lambda (a,): Not(Variable(a)),
2.138 + NOT: lambda (a,): a}
2.139 + def negate(f):
2.140 + return negmap[f.id](f.values)
2.141 + if not isinstance(formula, Operator):
2.142 + return formula
2.143 + if formula.id == NOT:
2.144 + return negate(formula.values[0])
2.145 + return formula.__class__(*values(de_morgan, formula))
2.146 +
2.147 +def reduce_iter(formula):
2.148 + redset = set(((BOX, BOX), (DIAMOND, DIAMOND), (DIAMOND, BOX), (BOX, DIAMOND)))
2.149 + if not isinstance(formula, Operator) or not isinstance(formula.values[0], Operator):
2.150 + return formula
2.151 + ids = (formula.id, formula.values[0].id)
2.152 + if ids in redset:
2.153 + return reduce_iter(formula.values[0])
2.154 + return formula.__class__(*values(reduce_iter, formula))
2.155 +
2.156 +def dist_mod(formula):
2.157 + distmap = {(BOX, AND): lambda (a, b): And(Box(a), Box(b)),
2.158 + (DIAMOND, OR): lambda (a, b): Or(Diamond(a), Diamond(b)),
2.159 + (BOX, OR): lambda (a, b): (isinstance(a, Box) or isinstance(b, Box))
2.160 + and Or(Box(a), Box(b)) or Box(Or(a, b)),
2.161 + (DIAMOND, AND): lambda (a, b): (isinstance(a, Diamond) or isinstance(b, Diamond))
2.162 + and And(Diamond(a), Diamond(b)) or Diamond(And(a, b))}
2.163 + if not isinstance(formula, Operator) or not isinstance(formula.values[0], Operator):
2.164 + return formula
2.165 + ids = (formula.id, formula.values[0].id)
2.166 + if ids in distmap:
2.167 + return distmap[ids](values(dist_mod, formula.values[0]))
2.168 + return formula.__class__(*values(dist_mod, formula))
2.169 +
2.170 +def mcnf(formula):
2.171 + if formula.id == OR:
2.172 + conj_id = int(formula.values[1].id == AND)
2.173 + return And(Or(formula.values[conj_id].values[0], formula.values[1 - conj_id]),
2.174 + Or(formula.values[conj_id].values[1], formula.values[1 - conj_id]));
2.175 + return formula
2.176 +
2.177 +def cnf(formula):
2.178 + def part(f):
2.179 + for v in f.values:
2.180 + if v.id != VARIABLE:
2.181 + sub_formulae.append(v)
2.182 + part(v)
2.183 +
2.184 + def conjunct(f):
2.185 + if len(bicon):
2.186 + return And(f, conjunct(bicon.pop()))
2.187 + return f
2.188 +
2.189 + sub_formulae = [formula]
2.190 + part(formula)
2.191 + sub_formulae.reverse()
2.192 + known = {}
2.193 + bicon = []
2.194 + for s in sub_formulae:
2.195 + for i, v in enumerate(s.values):
2.196 + if v in known:
2.197 + s.values[i] = known[v]
2.198 + bicon.append(Eq(Variable("x" + str(len(bicon))), s))
2.199 + known[s] = bicon[-1].values[0]
2.200 + return mcnf(dist_mod(reduce_iter(de_morgan(expand_imp_eq(conjunct(known[formula]))))))
2.201 +
2.202 +""" The Parser - Do NOT look below this line! """
2.203 +import re
2.204 +
2.205 +NOT = "~"
2.206 +AND = "&"
2.207 +OR = "|"
2.208 +IMP = "->"
2.209 +EQ = "<->"
2.210 +BOX = "[]"
2.211 +DIAMOND = "<>"
2.212 +VARIABLE = "VAR"
2.213 +LB = "("
2.214 +RB = ")"
2.215 +
2.216 +class Operator(object):
2.217 + def __init__(self, id, arity, *values):
2.218 + self.id = id
2.219 + self.arity = arity
2.220 + self.values = list(values)
2.221 + def depth(self):
2.222 + if self.arity == 0:
2.223 + return 0
2.224 + if self.id in (BOX, DIAMOND):
2.225 + return 1 + self.values[0].depth()
2.226 + if self.id == NOT:
2.227 + return self.values[0].depth()
2.228 + return max(self.values[0].depth(), self.values[1].depth())
2.229 + def __eq__(self, other):
2.230 + return self.id == other.id and self.values == other.values # commutative types not considered yet
2.231 + def __str__(self):
2.232 + out = self.id
2.233 + if self.arity == 0:
2.234 + out = str(self.values[0])
2.235 + if self.arity == 1:
2.236 + out = self.id + str(self.values[0])
2.237 + elif self.arity == 2:
2.238 + out = "(" + str(self.values[0]) + " " + self.id + " " + str(self.values[1]) + ")"
2.239 + return out
2.240 +
2.241 +class Not(Operator):
2.242 + arity = 1
2.243 + def __init__(self, *values):
2.244 + Operator.__init__(self, NOT, Not.arity, *values)
2.245 + def __call__(self):
2.246 + pass
2.247 +
2.248 +class And(Operator):
2.249 + arity = 2
2.250 + def __init__(self, *values):
2.251 + Operator.__init__(self, AND, And.arity, *values)
2.252 +
2.253 +class Or(Operator):
2.254 + arity = 2
2.255 + def __init__(self, *values):
2.256 + Operator.__init__(self, OR, Or.arity, *values)
2.257 +
2.258 +class Imp(Operator):
2.259 + arity = 2
2.260 + def __init__(self, *values):
2.261 + Operator.__init__(self, IMP, Imp.arity, *values)
2.262 +
2.263 +class Eq(Operator):
2.264 + arity = 2
2.265 + def __init__(self, *values):
2.266 + Operator.__init__(self, EQ, Eq.arity, *values)
2.267 +
2.268 +class Box(Operator):
2.269 + arity = 1
2.270 + def __init__(self, *values):
2.271 + Operator.__init__(self, BOX, Box.arity, *values)
2.272 +
2.273 +class Diamond(Operator):
2.274 + arity = 1
2.275 + def __init__(self, *values):
2.276 + Operator.__init__(self, DIAMOND, Diamond.arity, *values)
2.277 +
2.278 +class Variable(Operator):
2.279 + arity = 0
2.280 + def __init__(self, *values):
2.281 + Operator.__init__(self, VARIABLE, Variable.arity, *values)
2.282 +
2.283 +class Lb(Operator):
2.284 + arity = -1
2.285 + def __init__(self, *values):
2.286 + Operator.__init__(self, LB, Lb.arity, *values)
2.287 +
2.288 +class Rb(Operator):
2.289 + arity = -1
2.290 + def __init__(self, *values):
2.291 + Operator.__init__(self, RB, Rb.arity, *values)
2.292 +
2.293 +class Formula(object):
2.294 + def __init__(self):
2.295 + pass
2.296 +
2.297 +TOKENS = {re.compile("\("): Lb,
2.298 + re.compile("\)"): Rb,
2.299 + re.compile("~"): Not,
2.300 + re.compile("&"): And,
2.301 + re.compile("\|"): Or,
2.302 + re.compile("->"): Imp,
2.303 + re.compile("<->"): Eq,
2.304 + re.compile("\[\]"): Box,
2.305 + re.compile("<>"): Diamond,
2.306 + re.compile("[a-z]+"): Variable}
2.307 +
2.308 +class Scanner(object):
2.309 + def __init__(self, tokens, source=None):
2.310 + self.tokens = tokens
2.311 + self.reset(source)
2.312 + def next(self):
2.313 + while self.i < len(self.source):
2.314 + re.purge()
2.315 + for p, token in self.tokens.iteritems():
2.316 + m = p.match(self.source[self.i:])
2.317 + if m:
2.318 + self.i += m.end(0)
2.319 + value = m.group(0)
2.320 + return (token, value)
2.321 + self.i += 1
2.322 + return (None, None)
2.323 + def reset(self, source):
2.324 + self.i = 0
2.325 + self.source = source
2.326 +
2.327 +S = Formula
2.328 +A = "A"
2.329 +#0 S: (Variable,),
2.330 +#1 S: (Not, S),
2.331 +#2 S: (Lb, A, Rb),
2.332 +#3 S: (Box, S),
2.333 +#4 S: (Diamond, S),
2.334 +#5 A: (S, And, S),
2.335 +#6 A: (S, Or, S),
2.336 +#7 A: (S, Imp, S),
2.337 +#8 A: (S, Eq, S)
2.338 +SYNTAX = ((Variable,),
2.339 + (Not, S),
2.340 + (Lb, A, Rb),
2.341 + (Box, S),
2.342 + (Diamond, S),
2.343 + (S, And, S),
2.344 + (S, Or, S),
2.345 + (S, Imp, S),
2.346 + (S, Eq, S))
2.347 +
2.348 +class Parser(object):
2.349 + def __init__(self, syntax, scanner):
2.350 + self.syntax = syntax
2.351 + self.scanner = scanner
2.352 + def parse(self, source):
2.353 + table = {(S, Variable): 0,
2.354 + (S, Not): 1,
2.355 + (S, Lb): 2,
2.356 + (S, Box): 3,
2.357 + (S, Diamond): 4,
2.358 + (A, S, And): 5,
2.359 + (A, S, Or): 6,
2.360 + (A, S, Imp): 7,
2.361 + (A, S, Eq): 8,
2.362 + (A, Variable, And): 5,
2.363 + (A, Variable, Or): 6,
2.364 + (A, Variable, Imp): 7,
2.365 + (A, Variable, Eq): 8,
2.366 + (A, Not, Variable, And): 5,
2.367 + (A, Not, Variable, Or): 6,
2.368 + (A, Not, Variable, Imp): 7,
2.369 + (A, Not, Variable, Eq): 8}
2.370 + stack = [S]
2.371 + tree_nodes = []
2.372 + tree = None
2.373 + out = []
2.374 + self.scanner.reset(source)
2.375 + (token, value) = self.scanner.next()
2.376 + (lookahead, la_value) = self.scanner.next()
2.377 + (lookahead2, la2_value) = self.scanner.next()
2.378 + accepted = True
2.379 + while token and len(stack):
2.380 + top = stack[-1]
2.381 + if top == token:
2.382 + tree_nodes.append((token, value))
2.383 + stack.pop()
2.384 + #tree_nodes.append((stack.pop(), value))
2.385 + (token, value) = (lookahead, la_value)
2.386 + (lookahead, la_value) = (lookahead2, la2_value)
2.387 + #(lookahead, _) = self.scanner.next()
2.388 + (lookahead2, la2_value) = self.scanner.next()
2.389 + else:
2.390 + action = None
2.391 + if (top, token) in table:
2.392 + action = table[(top, token)]
2.393 + elif (top, token, lookahead) in table:
2.394 + action = table[(top, token, lookahead)]
2.395 + elif (top, token, lookahead, lookahead2) in table:
2.396 + action = table[(top, token, lookahead, lookahead2)]
2.397 + if action is None:
2.398 + accepted = False
2.399 + break
2.400 +
2.401 + out.append(action)
2.402 + stack.pop()
2.403 + stack.extend(reversed(self.syntax[action]))
2.404 + accepted = accepted and not len(stack)
2.405 + return (accepted, out, tree_nodes)
2.406 +
2.407 +class SyntaxTree(object):
2.408 + def __init__(self, seq, tree_nodes):
2.409 + seq.reverse()
2.410 + tree_nodes.reverse()
2.411 + self.root = self.compile(seq, tree_nodes)[0]
2.412 + def compile(self, seq, tree_nodes):
2.413 + stack = []
2.414 + while len(seq):
2.415 + s = seq.pop()
2.416 + if s == 0:
2.417 + c, v = tree_nodes.pop()
2.418 + while c != Variable:
2.419 + c, v = tree_nodes.pop()
2.420 + stack.append(Variable(v))
2.421 + elif s == 1:
2.422 + stack.append(Not(self.compile(seq, tree_nodes)))
2.423 + elif s == 2:
2.424 + stack.extend(self.compile(seq, tree_nodes))
2.425 + elif s == 3:
2.426 + stack.append(Box(self.compile(seq, tree_nodes)))
2.427 + elif s == 4:
2.428 + stack.append(Diamond(self.compile(seq, tree_nodes)))
2.429 + elif s == 5:
2.430 + stack.append(And(self.compile(seq, tree_nodes)))
2.431 + elif s == 6:
2.432 + stack.append(Or(self.compile(seq, tree_nodes)))
2.433 + elif s == 7:
2.434 + stack.append(Imp(self.compile(seq, tree_nodes)))
2.435 + elif s == 8:
2.436 + stack.append(Eq(self.compile(seq, tree_nodes)))
2.437 + return stack
2.438 +
2.439 +from argparse import ArgumentParser
2.440 +
2.441 +def parse_arguments():
2.442 + parser = ArgumentParser(description="Tries to parse simple modal logic syntax and reduces the formula. Edit your formula directly in the code file for best results.")
2.443 + parser.add_argument("-f", "--formula", help="provide your own formula here, or better don't")
2.444 + parser.add_argument("-d", "--dimacs", help="output file for the CNF DIMACS output")
2.445 + return parser.parse_args()
2.446 +
2.447 +EXCUSE = """The formula was not accepted by me, because I do not like you. It is not personal. I seem to have some issues with formulae with inner nesting and I generally suffer from my hasty implementation. I would like to thank you for your coorporation by providing the formula within the code file itself. Thank you. ~The Parser"""
2.448 +
2.449 +if __name__ == "__main__":
2.450 + main()
3.1 Binary file downloads/tim.zip has changed
4.1 --- a/factory/v2011/linksend.html Sat Jul 16 12:52:33 2011 +0200
4.2 +++ b/factory/v2011/linksend.html Mon Jul 18 01:51:06 2011 +0200
4.3 @@ -44,6 +44,7 @@
4.4 <h2>Recursive</h2>
4.5 <ul>
4.6 <li>
4.7 -This End.
4.8 +This End.<br />
4.9 +Recursive humour recycled from <a href="http://www4.informatik.tu-muenchen.de/~schulz/">Stephan Schulz</a>, to preserve the environment.
4.10 </li>
4.11 </ul>
5.1 --- a/factory/v2011/personalwork.html Sat Jul 16 12:52:33 2011 +0200
5.2 +++ b/factory/v2011/personalwork.html Mon Jul 18 01:51:06 2011 +0200
5.3 @@ -1,4 +1,42 @@
5.4 -<p>Here an unfiltered, random stack of small hacks, that I've produced for recreational reasons.</p>
5.5 +<p>Here is an unfiltered, undocumented, user-unfriendly random stack of little hacks, that I've produced for recreational reasons. I put them online in spirit of the liberation of useless machines.</p>
5.6 +
5.7 +<h2><a name="tim">Tim</a></h2>
5.8 +<ul>
5.9 +<li>
5.10 +<h4>Description</h4>
5.11 +A console-based head-on time recording tool, because time is money.</li>
5.12 +<li>
5.13 +<h4>Version</h4>
5.14 +A stable prototype, no output generation yet.
5.15 +<div class="download"><a href="downloads/tim.zip">
5.16 + Download Tim (Python and SQLite3 required)</a></div></li>
5.17 +</ul>
5.18 +
5.19 +<h2><a name="cau">Cau</a></h2>
5.20 +<img src="images/cau.png" alt="cau screenshot" class="float-right" height="150" width="200" />
5.21 +<ul>
5.22 +<li>
5.23 +<h4>Description</h4>
5.24 +Is bad PI-approximation good enough? A cellular automaton-based PI-approximation machine with Tkinter-based visualisation.</li>
5.25 +<li>
5.26 +<h4>Version</h4>
5.27 +Working, approximating, slowly visualising.
5.28 +<div class="download"><a href="downloads/cau.zip">
5.29 + Download Cau (Python and Tkinter required)</a></div></li>
5.30 +</ul>
5.31 +
5.32 +<h2><a name="sml">SML</a></h2>
5.33 +<ul>
5.34 +<li>
5.35 +<h4>Description</h4>
5.36 +A simple Simple Modal Logic library doing all kinds of reductions including Modal Conjunctive Normal Form. It also features DIMACS output and a satisfiability test via MiniSat2.</li>
5.37 +<li>
5.38 +<h4>Version</h4>
5.39 +Reliable version except for features noted as alpha. Parser has too much personality.
5.40 +<div class="download"><a href="downloads/sml.py">
5.41 + Download SML (Python and MiniSat2 required)</a></div></li>
5.42 +</ul>
5.43 +
5.44 <h2><a name="netchannel">NetChannel</a></h2>
5.45 <ul>
5.46 <li>
5.47 @@ -6,9 +44,9 @@
5.48 NetChannel is a simple Python object for message-based network communication
5.49 on the TCP/IP stack. NetChannel is based on stateful sessions for improved performance.</li>
5.50 <li>
5.51 -<h4>Version 0.7</h4>
5.52 +<h4>Version</h4>
5.53 A stable prototype.
5.54 -<div class="download"><a href="http://me73.com/downloads/netchannel.zip">
5.55 +<div class="download"><a href="downloads/netchannel.zip">
5.56 Download NetChannel (Python required)</a></div></li>
5.57 </ul>
5.58
5.59 @@ -20,9 +58,9 @@
5.60 Eden Plotter or Eden One is a quick prototype for my genetic programming routines.
5.61 It approximates a given function by the methods of GP.</li>
5.62 <li>
5.63 -<h4>Version 0.9</h4>
5.64 +<h4>Version</h4>
5.65 An unendurable slow prototype.
5.66 -<div class="download"><a href="http://me73.com/downloads/eden.zip">
5.67 +<div class="download"><a href="downloads/eden.zip">
5.68 Download Eden Plotter (Python required)</a></div></li>
5.69 </ul>
5.70
5.71 @@ -47,11 +85,11 @@
5.72 <li>
5.73 <h4>Version Antquarium Prototype</h4>
5.74 Antquarium is the predecessor of ANQ. It was an extended course assignment and serves as a prototype for ANQ.
5.75 -<div class="download"><a href="http://me73.com/downloads/antquarium.zip">
5.76 +<div class="download"><a href="downloads/antquarium.zip">
5.77 Download Antquarium for Windows (Python and .Net 2.0 required)</a></div>
5.78 </li>
5.79 <li>
5.80 -<h4>Version 0.4.1</h4>
5.81 +<h4>Version</h4>
5.82 Stable version missing features like flow and runtime topology control.
5.83 </li>
5.84 </ul>
5.85 @@ -70,7 +108,8 @@
5.86 </li>
5.87 <li>
5.88 <h4>Requirements</h4>
5.89 -<ul><li>Microsoft Windows XP or Vista</li><li>Graphics card supporting OpenGL 1.3 or higher</li><li>One keyboard with at least the arrow keys working</li></ul> <div class="download"><a href="http://me73.com/downloads/ThemeBlocksSetup.exe">Download Theme Blocks (1.4MB)</a></div>
5.90 +<ul><li>Microsoft Windows XP or Vista</li><li>Graphics card supporting OpenGL 1.3 or higher</li><li>One keyboard with at least the arrow keys working</li></ul>
5.91 +<div class="download"><a href="downloads/ThemeBlocksSetup.exe">Download Theme Blocks (1.4MB)</a></div>
5.92 </li>
5.93 </ul>
5.94
5.95 @@ -84,9 +123,9 @@
5.96 <li>
5.97 <h4>Features</h4>
5.98 <ul><li>Three AI difficulty levels</li><li>Endless gameplay, play till you're bored!</li></ul>
5.99 -<div class="download"><a href="http://me73.com/downloads/PyngPongSetup.exe">
5.100 +<div class="download"><a href="downloads/PyngPongSetup.exe">
5.101 Download Pyng Pong for Windows (2.4MB)</a></div>
5.102 -<div class="download"><a href="http://me73.com/downloads/pyngpong.zip">
5.103 +<div class="download"><a href="downloads/pyngpong.zip">
5.104 Download Pyng Pong Source (Python + PyGame needed) (0.4MB)</a></div>
5.105 </li>
5.106 </ul>
5.107 @@ -101,7 +140,7 @@
5.108 <li>
5.109 <h4>Features</h4>
5.110 <ul><li>Drag, create and remove sound sources</li><li>Height of placement sets the sound pitch</li></ul>
5.111 -<div class="download"><a href="http://me73.com/downloads/klangbild.zip">
5.112 +<div class="download"><a href="downloads/klangbild.zip">
5.113 Download Klangbild (Python and PyGlet required)</a></div>
5.114 </li></ul>
5.115