1
0

urlmanager

This commit is contained in:
Mike Schwörer 2017-11-09 17:43:34 +01:00
parent faee825af9
commit 8fb7bb51b0
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
115 changed files with 1512 additions and 181 deletions

0
www/data/images/.gitkeep Normal file
View File

View File

Before

Width:  |  Height:  |  Size: 395 B

After

Width:  |  Height:  |  Size: 395 B

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

View File

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 135 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 131 KiB

View File

Before

Width:  |  Height:  |  Size: 198 KiB

After

Width:  |  Height:  |  Size: 198 KiB

View File

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

View File

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 200 KiB

After

Width:  |  Height:  |  Size: 200 KiB

View File

Before

Width:  |  Height:  |  Size: 194 KiB

After

Width:  |  Height:  |  Size: 194 KiB

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

Before

Width:  |  Height:  |  Size: 262 KiB

After

Width:  |  Height:  |  Size: 262 KiB

View File

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

View File

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 164 KiB

View File

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 124 KiB

View File

View File

@ -1,6 +1,6 @@
<div class="headerdiv">
<div class="logowrapper">
<img class="logo" src="/images/logo.png" />
<img class="logo" src="/data/images/logo.png" />
</div>
<div class="tabrow">

View File

@ -1,137 +1,131 @@
<?php
require_once (__DIR__ . '/internals/base.php');
$URL_RULES =
[
[ 'url' => [], 'target' => 'pages/main.php', 'options' => [], ],
[ 'url' => ['index'], 'target' => 'pages/main.php', 'options' => [], ],
[ 'url' => ['index.php'], 'target' => 'pages/main.php', 'options' => [], ],
[ 'url' => ['msmain', 'index'], 'target' => 'pages/main.php', 'options' => [], ],
[ 'url' => ['about'], 'target' => 'pages/about.php', 'options' => [], ],
[ 'url' => ['msmain', 'about'], 'target' => 'pages/about.php', 'options' => [], ],
[ 'url' => ['programs'], 'target' => 'pages/programs_list.php', 'options' => [ 'categoryfilter' => '' ], ],
[ 'url' => ['programs', 'index'], 'target' => 'pages/programs_list.php', 'options' => [ 'categoryfilter' => '%GET%' ], ],
[ 'url' => ['programs', 'index'], 'target' => 'pages/programs_list.php', 'options' => [ 'categoryfilter' => '' ], ],
[ 'url' => ['programs', 'cat', '?{categoryfilter}'], 'target' => 'pages/programs_list.php', 'options' => [ 'categoryfilter' => '%URL%' ], ],
[ 'url' => ['downloads', 'details.php'], 'target' => 'pages/programs_list.php', 'options' => [ 'categoryfilter' => '' ], ],
[ 'url' => ['downloads', 'downloads.php'], 'target' => 'pages/programs_list.php', 'options' => [ 'categoryfilter' => '' ], ],
[ 'url' => ['programs', 'view', '?{id}'], 'target' => 'pages/programs_view.php', 'options' => [ 'id' => '%URL%' ], ],
[ 'url' => ['programs', 'view'], 'target' => 'pages/programs_view.php', 'options' => [ 'id' => '%GET%' ], ],
[ 'url' => ['downloads', '?{id}'], 'target' => 'pages/programs_download.php', 'options' => [ 'id' => '%URL%' ], ],
[ 'url' => ['programs', 'download', '?{id}'], 'target' => 'pages/programs_download.php', 'options' => [ 'id' => '%URL%' ], ],
[ 'url' => ['programs', 'download'], 'target' => 'pages/programs_download.php', 'options' => [ 'id' => '%GET%' ], ],
[ 'url' => ['update.php'], 'target' => 'pages/api_updatecheck.php', 'options' => [ 'Name' => '%GET%' ], ],
[ 'url' => ['update.php', '?{Name}'], 'target' => 'pages/api_updatecheck.php', 'options' => [ 'Name' => '%URL%' ], ],
[ 'url' => ['update'], 'target' => 'pages/api_updatecheck.php', 'options' => [ 'Name' => '%GET%' ], ],
[ 'url' => ['update', '?{Name}'], 'target' => 'pages/api_updatecheck.php', 'options' => [ 'Name' => '%URL%' ], ],
[ 'url' => ['update2'], 'target' => 'pages/api_updatecheck.php', 'options' => [ 'Name' => '%GET%' ], ],
[ 'url' => ['api', 'update'], 'target' => 'pages/api_updatecheck.php', 'options' => [ 'Name' => '%GET%' ], ],
[ 'url' => ['api', 'update', '?{Name}'], 'target' => 'pages/api_updatecheck.php', 'options' => [ 'Name' => '%URL%' ], ],
[ 'url' => ['api', 'test'], 'target' => 'pages/api_test.php', 'options' => [], ],
[ 'url' => ['api', 'setselfadress'], 'target' => 'pages/api_setselfadress.php', 'options' => [], ],
[ 'url' => ['msmain', 'admin', 'egh', '?{commandcode}'], 'target' => 'pages/admin_egh.php', 'options' => [ 'commandcode' => '%URL%' ], ],
[ 'url' => ['msmain', 'adminEGH'], 'target' => 'pages/admin_egh.php', 'options' => [ 'commandcode' => '%GET%' ], ],
[ 'url' => ['blog'], 'target' => 'pages/blog_list.php', 'options' => [], ],
[ 'url' => ['blogpost', 'index'], 'target' => 'pages/blog_list.php', 'options' => [], ],
[ 'url' => ['blog', '?{id}'], 'target' => 'pages/blog_view.php', 'options' => [ 'id' => '%URL%', 'subview' => '' ], ],
[ 'url' => ['blog', '?{id}'], 'target' => 'pages/blog_view.php', 'options' => [ 'id' => '%URL%', 'subview' => '' ], ],
[ 'url' => ['blog', '?{id}', '?{name}'], 'target' => 'pages/blog_view.php', 'options' => [ 'id' => '%URL%', 'subview' => '' ], ],
[ 'url' => ['blog', '?{id}', '?{name}', '?{subview}'], 'target' => 'pages/blog_view.php', 'options' => [ 'id' => '%URL%', 'subview' => '%URL%' ], ],
[ 'url' => ['blogpost', 'view'], 'target' => 'pages/blog_view.php', 'options' => [ 'id' => '%GET%', 'subview' => '' ], ],
[ 'url' => ['highscores', 'list.php'], 'target' => 'pages/highscores_listentries.php', 'options' => [ 'gameid' => '%GET%' ], ],
[ 'url' => ['highscores', 'list'], 'target' => 'pages/highscores_listentries.php', 'options' => [ 'gameid' => '%GET%' ], ],
[ 'url' => ['highscores', 'listentries'], 'target' => 'pages/highscores_listentries.php', 'options' => [ 'gameid' => '%GET%' ], ],
[ 'url' => ['highscores', 'list.php'], 'target' => 'pages/highscores_listgames.php', 'options' => [], ],
[ 'url' => ['highscores', 'list'], 'target' => 'pages/highscores_listgames.php', 'options' => [], ],
[ 'url' => ['highscores', 'listgames'], 'target' => 'pages/highscores_listgames.php', 'options' => [], ],
[ 'url' => ['highscores', 'insert.php'], 'target' => 'pages/highscores_insert.php', 'options' => [ 'gameid' => '%GET%', 'check' => '%GET%', 'name' => '%GET%', 'rand' => '%GET%', 'points' => '%GET%' ], ],
[ 'url' => ['highscores', 'insert'], 'target' => 'pages/highscores_insert.php', 'options' => [ 'gameid' => '%GET%', 'check' => '%GET%', 'name' => '%GET%', 'rand' => '%GET%', 'points' => '%GET%' ], ],
[ 'url' => ['highscores', 'update.php'], 'target' => 'pages/highscores_update.php', 'options' => [ 'gameid' => '%GET%', 'check' => '%GET%', 'name' => '%GET%', 'rand' => '%GET%', 'points' => '%GET%', 'nameid' => '%GET%' ], ],
[ 'url' => ['highscores', 'update'], 'target' => 'pages/highscores_update.php', 'options' => [ 'gameid' => '%GET%', 'check' => '%GET%', 'name' => '%GET%', 'rand' => '%GET%', 'points' => '%GET%', 'nameid' => '%GET%' ], ],
[ 'url' => ['highscores', 'list_top50.php'], 'target' => 'pages/highscores_top50.php', 'options' => [ 'gameid' => '%GET%' ], ],
[ 'url' => ['highscores', 'list_top50'], 'target' => 'pages/highscores_top50.php', 'options' => [ 'gameid' => '%GET%' ], ],
[ 'url' => ['highscores', 'getNewID.php'], 'target' => 'pages/highscores_newid.php', 'options' => [ 'gameid' => '%GET%' ], ],
[ 'url' => ['highscores', 'newid'], 'target' => 'pages/highscores_newid.php', 'options' => [ 'gameid' => '%GET%' ], ],
[ 'url' => ['404'], 'target' => 'pages/error_404.php', 'options' => [], ],
];
//#############################################################################
$path = strtolower(parse_url($_SERVER['REQUEST_URI'])['path']);
$pathparts = preg_split('@/@', $path, NULL, PREG_SPLIT_NO_EMPTY);
$partcount = count($pathparts);
global $OPTIONS;
// [/]
if ($partcount == 0)
foreach ($URL_RULES as $rule)
{
if ($partcount !== count($rule['url'])) continue;
$urlparams = [];
$match = true;
for($i = 0; $i < $partcount; $i++)
{
$comp = $rule['url'][$i];
if (startsWith($comp, '?{') && endsWith($comp, '}'))
{
$ident = substr($comp, 2, strlen($comp)-3);
$urlparams[$ident] = $pathparts[$i];
}
else
{
if (strtolower($comp) !== strtolower($pathparts[$i])) { $match = false; break; }
}
}
if (!$match) continue;
$opt = [];
foreach($rule['options'] as $optname => $optvalue)
{
$value = $optvalue;
if ($value === '%GET%')
{
if (!isset($_GET[$optname])) { $match = false; break; }
$value = $_GET[$optname];
}
else if ($value === '%POST%')
{
if (!isset($_POST[$optname])) { $match = false; break; }
$value = $_POST[$optname];
}
else if ($value === '%URL%')
{
if (!isset($urlparams[$optname])) { $match = false; break; }
$value = $urlparams[$optname];
}
$opt[strtolower($optname)] = $value;
}
if (!$match) continue;
$OPTIONS = $opt;
include $rule['target'];
return;
}
{
// [404] - Page Not Found
$OPTIONS = [];
include 'pages/main.php';
include 'pages/error_404.php';
return;
}
// [programs/]
if ($partcount == 1 && $pathparts[0] == 'programs')
{
$OPTIONS = [];
include 'pages/programs_list.php';
return;
}
// [programs/cat/<categoryfilter>]
if ($partcount == 3 && $pathparts[0] == 'programs' && $pathparts[1] == 'cat')
{
$OPTIONS = [ 'categoryfilter' => $pathparts[2] ];
include 'pages/programs_list.php';
return;
}
// [programs/view/<id>]
if ($partcount == 3 && $pathparts[0] == 'programs' && $pathparts[1] == 'view')
{
$OPTIONS = [ 'id' => $pathparts[2] ];
include 'pages/programs_view.php';
return;
}
// [programs/download/<id>]
if ($partcount == 3 && $pathparts[0] == 'programs' && $pathparts[1] == 'download')
{
$OPTIONS = [ 'id' => $pathparts[2] ];
include 'pages/programs_download.php';
return;
}
// [log/]
if ($partcount == 1 && $pathparts[0] == 'log')
{
$OPTIONS = [ 'id' => -1 ];
include 'pages/log.php';
return;
}
// [log/<id>]
if ($partcount == 2 && $pathparts[0] == 'log')
{
$OPTIONS = [ 'id' => $pathparts[1] ];
include 'pages/log.php';
return;
}
// [update.php]
if ($partcount == 1 && $pathparts[0] == 'update.php')
{
$OPTIONS = [ 'name' => '' ];
include 'pages/updatecheck.php';
return;
}
// [update.php/<name>]
if ($partcount == 2 && $pathparts[0] == 'update.php')
{
$OPTIONS = [ 'name' => $pathparts[1] ];
include 'pages/updatecheck.php';
return;
}
// [update/]
if ($partcount == 1 && $pathparts[0] == 'update')
{
$OPTIONS = [ 'name' => '' ];
include 'pages/updatecheck.php';
return;
}
// [update/<name>]
if ($partcount == 2 && $pathparts[0] == 'update')
{
$OPTIONS = [ 'name' => $pathparts[1] ];
include 'pages/updatecheck.php';
return;
}
// [blog/]
if ($partcount == 1 && $pathparts[0] == 'blog')
{
$OPTIONS = [];
include 'pages/blog_list.php';
return;
}
// [blog/<id>]
if ($partcount == 2 && $pathparts[0] == 'blog')
{
$OPTIONS = [ 'id' => $pathparts[1] ];
include 'pages/blog_view.php';
return;
}
// [blog/<id>/<name>]
if ($partcount == 3 && $pathparts[0] == 'blog')
{
$OPTIONS = [ 'id' => $pathparts[1], 'subview' => '' ];
include 'pages/blog_view.php';
return;
}
// [blog/<id>/<name>/<subview>]
if ($partcount == 4 && $pathparts[0] == 'blog')
{
$OPTIONS = [ 'id' => $pathparts[1], 'subview' => $pathparts[3] ];
include 'pages/blog_view.php';
return;
}
// [msmain/admin/egh/<commandcode>]
if ($partcount == 4 && $pathparts[0] == 'msmain' && $pathparts[1] == 'admin' && $pathparts[2] == 'egh')
{
$OPTIONS = [ 'commandcode' => $pathparts[3] ];
include 'pages/egh.php';
return;
}
die("Invalid path:" . $path); //TODO

View File

@ -1,20 +1,21 @@
<?php if(count(get_included_files()) ==1) exit("Direct access not permitted.");
global $CONFIG;
$CONFIG = require 'config.php';
$PDO = NULL;
function connect()
function startsWith($haystack, $needle)
{
global $CONFIG;
global $PDO;
$dsn = "mysql:host=" . $CONFIG['host'] . ";dbname=" . $CONFIG['database'] . ";charset=utf8";
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
$PDO = new PDO($dsn, $CONFIG['user'], $CONFIG['password'], $opt);
$length = strlen($needle);
return (substr($haystack, 0, $length) === $needle);
}
function endsWith($haystack, $needle)
{
$length = strlen($needle);
return $length === 0 || (substr($haystack, -$length) === $needle);
}
function httpError($errorcode, $message)
{
die($message);//TODO errorcode
}

103
www/internals/database.php Normal file
View File

@ -0,0 +1,103 @@
<?php if(count(get_included_files()) ==1) exit("Direct access not permitted.");
class Database
{
/* @var PDO $PDO */
public static $PDO = NULL;
public static function connect()
{
global $CONFIG;
if (self::$PDO !== NULL) return;
$dsn = "mysql:host=" . $CONFIG['host'] . ";dbname=" . $CONFIG['database'] . ";charset=utf8";
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
self::$PDO = new PDO($dsn, $CONFIG['user'], $CONFIG['password'], $opt);
}
public static function sql_query_num($query)
{
$r = self::$PDO->query($query)->fetch(PDO::FETCH_NUM)[0];
return $r;
}
public static function sql_query_num_prep($query, $params)
{
$stmt = self::$PDO->prepare($query);
foreach ($params as $p)
{
if (strpos($query, $p[0]) !== FALSE) $stmt->bindValue($p[0], $p[1], $p[2]);
}
$stmt->execute();
$r = $stmt->fetch(PDO::FETCH_NUM)[0];
return $r;
}
public static function sql_query_assoc($query)
{
$r = self::$PDO->query($query)->fetchAll(PDO::FETCH_ASSOC);
return $r;
}
public static function sql_query_assoc_prep($query, $params)
{
$stmt = self::$PDO->prepare($query);
foreach ($params as $p)
{
if (strpos($query, $p[0]) !== FALSE) $stmt->bindValue($p[0], $p[1], $p[2]);
}
$stmt->execute();
$r = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $r;
}
public static function sql_query_single($query)
{
$r = self::$PDO->query($query)->fetch(PDO::FETCH_ASSOC);
return $r;
}
public static function sql_query_single_prep($query, $params)
{
$stmt = self::$PDO->prepare($query);
foreach ($params as $p)
{
if (strpos($query, $p[0]) !== FALSE) $stmt->bindValue($p[0], $p[1], $p[2]);
}
$stmt->execute();
$r = $stmt->fetch(PDO::FETCH_ASSOC);
return $r;
}
public static function sql_exec_prep($query, $params)
{
$stmt = self::$PDO->prepare($query);
foreach ($params as $p)
{
if (strpos($query, $p[0]) !== FALSE) $stmt->bindValue($p[0], $p[1], $p[2]);
}
$stmt->execute();
return $stmt->rowCount();
}
}

View File

@ -0,0 +1,12 @@
<?php if(count(get_included_files()) ==1) exit("Direct access not permitted.");
class Highscores
{
public static function generateChecksum($rand, $player, $playerid, $points, $gamesalt)
{
if ($playerid >= 0)
return md5($rand . $player . $playerid . $points . $gamesalt);
else
return md5($rand . $player . $points . $gamesalt);
}
}

View File

@ -0,0 +1,21 @@
<?php
function get_client_ip() {
if (getenv('HTTP_CLIENT_IP')) return getenv('HTTP_CLIENT_IP');
else if(getenv('HTTP_X_FORWARDED_FOR')) return getenv('HTTP_X_FORWARDED_FOR');
else if(getenv('HTTP_X_FORWARDED')) return getenv('HTTP_X_FORWARDED');
else if(getenv('HTTP_FORWARDED_FOR')) return getenv('HTTP_FORWARDED_FOR');
else if(getenv('HTTP_FORWARDED')) return getenv('HTTP_FORWARDED');
else if(getenv('REMOTE_ADDR')) return getenv('REMOTE_ADDR');
else if (isset($_SERVER['HTTP_CLIENT_IP'])) return $_SERVER['HTTP_CLIENT_IP'];
else if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])) return $_SERVER['HTTP_X_FORWARDED_FOR'];
else if(isset($_SERVER['HTTP_X_FORWARDED'])) return $_SERVER['HTTP_X_FORWARDED'];
else if(isset($_SERVER['HTTP_FORWARDED_FOR'])) return $_SERVER['HTTP_FORWARDED_FOR'];
else if(isset($_SERVER['HTTP_FORWARDED'])) return $_SERVER['HTTP_FORWARDED'];
else if(isset($_SERVER['REMOTE_ADDR'])) return $_SERVER['REMOTE_ADDR'];
else return 'UNKNOWN';
}
//TODO setSelfAdress
echo 'Ok.';

3
www/pages/api_test.php Normal file
View File

@ -0,0 +1,3 @@
<?php
echo "{}";

View File

@ -0,0 +1,18 @@
<?php
global $OPTIONS;
require_once (__DIR__ . '/../internals/base.php');
require_once (__DIR__ . '/../internals/database.php');
$name = $OPTIONS['name'];
Database::connect();
$data = Database::sql_query_single_prep('SELECT * FROM ms4_updates WHERE Name = :n',
[
[':n', $name, PDO::PARAM_STR],
]);
if ($data == NULL) httpError(404, 'Invalid Request - [Name] not found');
print($data['Name']."<hr>".$data['Version']."<hr>".$data['Link']);

View File

@ -0,0 +1,39 @@
<?php
global $OPTIONS;
require_once (__DIR__ . '/../internals/base.php');
require_once (__DIR__ . '/../internals/database.php');
require_once (__DIR__ . '/../internals/highscores.php');
Database::connect();
$gameid = $OPTIONS['gameid'];
$check = $OPTIONS['check'];
$name = $OPTIONS['name'];
$rand = $OPTIONS['rand'];
$points = $OPTIONS['points'];
if (! is_numeric($gameid)) httpError(400, 'Invalid Request');
if (! is_numeric($points)) httpError(400, 'Invalid Request');
$game = Database::sql_query_single_prep('SELECT * FROM ms4_highscoregames WHERE ID = :id',
[
[ ':id', $OPTIONS['gameid'], PDO::PARAM_INT ],
]);
if ($game == NULL) httpError(400, 'Invalid Request');
$checksum_generated = Highscores::generateChecksum($rand, $name, -1, $points, $game['SALT']);
if ($checksum_generated != $check) die('Nice try !');
Database::sql_exec_prep('INSERT INTO ms4_highscoreentries (GAME_ID, POINTS, PLAYER, PLAYERID, CHECKSUM, TIMESTAMP, IP) VALUES (:gid, :p, :pn, :pid, :cs, :ts, :ip)',
[
[':gid', $gameid, PDO::PARAM_INT],
[':p', $points, PDO::PARAM_INT],
[':pn', $name, PDO::PARAM_STR],
[':pid', -1, PDO::PARAM_INT],
[':cs', $check, PDO::PARAM_STR],
[':ts', time(), PDO::PARAM_STR],
[':ip', $_SERVER['REMOTE_ADDR'], PDO::PARAM_STR],
]);
echo 'ok.';

View File

@ -0,0 +1,129 @@
<?php
global $OPTIONS;
require_once (__DIR__ . '/../internals/base.php');
require_once (__DIR__ . '/../internals/database.php');
require_once (__DIR__ . '/../internals/highscores.php');
Database::connect();
$pagesize = 20;
$start = 0;
$highlight = 0;
if (isset($_GET["start"]))
{
$start = intval(htmlspecialchars($_GET["start"])) - 1;
if ($start < 0) $start = 0;
}
if (isset($_GET["highlight"]))
{
$highlight= intval(htmlspecialchars($_GET["highlight"]));
}
$game = Database::sql_query_single_prep('SELECT * FROM ms4_highscoregames WHERE ID = :id',
[
[ ':id', $OPTIONS['gameid'], PDO::PARAM_INT ]
]);
$entries = Database::sql_query_assoc_prep('SELECT * FROM ms4_highscoreentries WHERE GAME_ID = :id ORDER BY POINTS DESC',
[
[ ':id', $OPTIONS['gameid'], PDO::PARAM_INT ]
]);
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta charset="utf-8"/>
<title>highscores</title>
<style type="text/css">
<!--
body {
background-color: #DDF;
padding: 1em 1em 0em;
}
table {
margin: auto;
width: 80%;
text-align: center;
border-spacing: 0px;
}
table td { padding: 2px 0px; }
table td { width: 25%; }
table td:last-child { width: 50%; }
caption {
font-weight: bolder;
text-decoration: underline;
font-size: x-large;
}
a {
color: #008;
text-decoration: underline;
}
a:hover { text-decoration: none; }
#headline > td { text-decoration: underline; }
#highlight {
font-weight: bolder;
background-color: #CCF;
}
-->
</style>
</head>
<body>
<table>
<caption><?php echo $game['NAME']; ?></caption>
<tr id="headline" >
<td>rank</td>
<td>points</td>
<td>name</td>
</tr>
<?php
$current = 0;
foreach ($entries as $entry)
{
$current++;
if ($current >= $start && $current - $start <= $pagesize)
{
if ($current == $highlight)
echo '<tr id="highlight">';
else
echo "<tr>";
echo "<td>$current</td>";
echo "<td>".$entry['POINTS']."</td>";
echo "<td>".$entry['PLAYER']."</td>";
echo "</tr>";
}
}
$more = max(0, $start - $pagesize);
$less = $start + $pagesize;
echo '<tr>';
if ($start > 0)
echo '<td><a href="' . "/Highscores/list?gameid=".$game['ID']."&start=$more&highlight=$highlight" . '">[more points]</a></td>';
else
echo '<td></td>';
echo '<td></td>';
if ($start + $pagesize < count($entries))
echo '<td><a href="' . "/Highscores/list?gameid=".$game['ID']."&start=$less&highlight=$highlight" . '">[less points]</a></td>';
else
echo '<td></td>';
echo '</tr>';
?>
</table>
</body>
</html>

View File

@ -0,0 +1,43 @@
<?php
global $OPTIONS;
require_once (__DIR__ . '/../internals/base.php');
require_once (__DIR__ . '/../internals/database.php');
require_once (__DIR__ . '/../internals/highscores.php');
Database::connect();
$games = Database::sql_query_assoc('SELECT * FROM ms4_highscoregames');
?>
<html>
<head>
<meta charset="utf-8"/>
<title>highscores</title>
<style type="text/css">
<!--
body {
background-color: #DDF;
padding: 1em 1em 0em;
}
a {
color: #008;
text-decoration: underline;
}
a:hover { text-decoration: none; }
-->
</style>
</head>
<body>
<?php
foreach ($games as $game)
{
echo '<a href="/Highscores/list?gameid=' . $game['ID'] . '">' . $game['NAME'] . '</a><br>' . "\r\n";
}
?>
</body>
</html>

View File

@ -0,0 +1,17 @@
<?php
global $OPTIONS;
require_once (__DIR__ . '/../internals/base.php');
require_once (__DIR__ . '/../internals/database.php');
require_once (__DIR__ . '/../internals/highscores.php');
Database::connect();
$newid = Database::sql_query_num_prep('SELECT MAX(PLAYERID)+1 AS NID FROM ms4_highscoreentries WHERE GAME_ID = :gid',
[
[ ':id', $OPTIONS['gameid'], PDO::PARAM_INT ]
]);
if ($newid < 1024) $newid = 1024;
print $newid;

View File

@ -0,0 +1,18 @@
<?php
global $OPTIONS;
require_once (__DIR__ . '/../internals/base.php');
require_once (__DIR__ . '/../internals/database.php');
require_once (__DIR__ . '/../internals/highscores.php');
Database::connect();
$entries = Database::sql_query_single_prep('SELECT * FROM ms4_highscoreentries WHERE GAME_ID = :id ORDER BY POINTS DESC LIMIT 50',
[
[ ':id', $OPTIONS['gameid'], PDO::PARAM_INT ]
]);
for ($i = 0; $i < count($entries); $i++)
{
print($entries[$i]['POINTS'] . '||' . htmlentities($entries[$i]['PLAYER']) . "\r\n");
}

View File

@ -0,0 +1,65 @@
<?php
global $OPTIONS;
require_once (__DIR__ . '/../internals/base.php');
require_once (__DIR__ . '/../internals/database.php');
require_once (__DIR__ . '/../internals/highscores.php');
Database::connect();
$gameid = $OPTIONS['gameid'];
$check = $OPTIONS['check'];
$name = $OPTIONS['name'];
$nameid = $OPTIONS['nameid'];
$rand = $OPTIONS['rand'];
$points = $OPTIONS['points'];
if (! is_numeric($gameid)) httpError(400, 'Invalid Request');
if (! is_numeric($nameid)) httpError(400, 'Invalid Request');
if (! is_numeric($points)) httpError(400, 'Invalid Request');
$game = Database::sql_query_single_prep('SELECT * FROM ms4_highscoregames WHERE ID = :id',
[
[ ':id', $OPTIONS['gameid'], PDO::PARAM_INT ],
]);
if ($game == NULL) httpError(400, 'Invalid Request');
$checksum_generated = Highscores::generateChecksum($rand, $name, $nameid, $points, $game['SALT']);
if ($checksum_generated != $check) die('Nice try !');
$old = Database::sql_query_single_prep('SELECT * FROM ms4_highscoreentries WHERE GAME_ID = :gid AND PLAYERID = :pid',
[
[ ':gid', $OPTIONS['gameid'], PDO::PARAM_INT ],
[ ':pid', $OPTIONS['nameid'], PDO::PARAM_INT ],
]);
if ($old == null)
{
Database::sql_exec_prep('INSERT INTO ms4_highscoreentries (GAME_ID, POINTS, PLAYER, PLAYERID, CHECKSUM, TIMESTAMP, IP) VALUES (:gid, :p, :pn, :pid, :cs, :ts, :ip)',
[
[':gid', $gameid, PDO::PARAM_INT],
[':p', $points, PDO::PARAM_INT],
[':pn', $name, PDO::PARAM_STR],
[':pid', $nameid, PDO::PARAM_INT],
[':cs', $check, PDO::PARAM_STR],
[':ts', time(), PDO::PARAM_STR],
[':ip', $_SERVER['REMOTE_ADDR'], PDO::PARAM_STR],
]);
echo 'ok.';
}
else
{
Database::sql_exec_prep('UPDATE ms4_highscoreentries SET POINTS = :p, PLAYER = :pn, CHECKSUM = :cs, IP = :ip, TIMESTAMP = :ts WHERE GAME_ID = :gid AND PLAYERID = :pid',
[
[':gid', $gameid, PDO::PARAM_INT],
[':p', $points, PDO::PARAM_INT],
[':pn', $name, PDO::PARAM_STR],
[':pid', $nameid, PDO::PARAM_INT],
[':cs', $check], PDO::PARAM_STR,
[':ts', time(), PDO::PARAM_STR],
[':ip', $_SERVER['REMOTE_ADDR'], PDO::PARAM_STR],
]);
echo 'ok.';
}

View File

@ -1,23 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<?php require __DIR__ . '/../internals/base.php' ?>
<?php require_once (__DIR__ . '/../internals/base.php'); ?>
<head>
<meta charset="utf-8">
<title>Mikescher.com</title>
<meta name="google-site-verification" content="pZOhmjeJcQbRMNa8xRLam4dwJ2oYwMwISY1lRKreSSs"/>
<link rel="icon" type="image/png" href="/images/favicon.png"/>
<link rel="stylesheet" href="/data/styles.css"/>
<meta charset="utf-8">
<title>Mikescher.com</title>
<meta name="google-site-verification" content="pZOhmjeJcQbRMNa8xRLam4dwJ2oYwMwISY1lRKreSSs"/>
<link rel="icon" type="image/png" href="/data/images/favicon.png"/>
<link rel="stylesheet" href="/data/css/styles.css"/>
</head>
<body>
<?php include (__DIR__ . '/../fragments/header.php'); ?>
<?php include (__DIR__ . '/../fragments/header.php'); ?>
<div id="content">
<div id="content">
<?php include (__DIR__ . '/../fragments/eulerpanel.php'); ?>
</div>
</div>
<?php include (__DIR__ . '/../fragments/footer.php'); ?>
<?php include (__DIR__ . '/../fragments/footer.php'); ?>
</body>
</html>

View File

@ -0,0 +1,485 @@
Let's do Befunge-93
=====================
Hello, this is my try to teach *you* a little bit of Befunge-93.
Preamble
--------
You may ask why someone should learn an esoteric language like Befunge-93 ?
It has close to no real world application and is far away from every other language you may already know.
But wait, Befunge has one really neat property *(at least in my opinion)*: its really fun to write in it. And that **because** it's totally different from every other language. Writing code in Befunge has a lot to do with planning and laying out your code.
Yes physically planning how your code will look and where you have to write specific subroutines.
But enough, let's go look at some code.
Chapter 1: A whole new dimension
--------------------------------
### Choosing the right tools
So first you will need an interpreter.
For the beginning I recommend to simply use an javascript interpreter - they are missing a lot of important features, like manually jump in your program, breakpoints, or high speed.
But they are easy too use, and for now you will write programs the size you don't need all these fancy features.
So just google for an online-interpreter - there are many out there, and if you need an more advanced interpreter later you can take a look at my own interpreter *BefunExec*.
### The simplest program
Okay let's start.
You will need an empty ASCII-encoded text file - this will be our code.
You have to understand that Befunge operates in an 2-dimensional space - like in normal programming you have a PC (Program Counter) that describes your current position.
In normal programs (you know, C, Python, Java etc.) this PC is 1-dimensional, in Befunge it's 2dimensional. At the beginning its positioned in the top-left corner and with every tick its moving one field to the left.
So every Character in your text-document is a command - the most simple command is a space. The space is the `NOP` Command - the No operation
> **NOTE:**
>
> - Befunge operates in a 2-dimensional Grid
> - The program-counter starts top-left and initially moves right
> - Every character in your document is then an individual command
So our first program can be as simple as that:
```befunge
```
### And it loops around
So an empty file is indeed a valid Befunge Program.
So this program does only execute NOP over and over again in an endless loop.
A Befunge-93 program has the size of 80x25, and when the PC reaches the right edge it just wraps around and comes back in on the left edge.
> **NOTE:**
>
> - Befunge-93 programs have a fixed size of 80x25
> - The Befunge space is infinite - it wraps around its edges
OK, but now we want to write *real* commands to our file - there are 4 basic commands ( `v` , `>` , `^` and `<` ).
With these "arrows" you are able to change the direction of the PC.
So with these commands we are now able to write a rather simple endless-loop program:
```befunge
> v
^ <
```
![img01][1]
### The stack
We now have a Grid where we can layout our program and a PC that runs through this grid to execute our program.
Befunge has one more element you should know about: **The stack**.
Every befunge program has a Stack you can manipulate, you can do all the normal stack operations on it, push, pop, peek etc.
The commands to push something on the stack are `0` - `9`. So if you write
```befunge
0 1 9 9
```
it will push a zero, a one, and two nines on the stack.
If you want to end the program after that you can use the **@** Command.
> **NOTE:**
>
> - You can push a digit to the stack with the commands `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`, `0`
> - The Command `@` stops the program
You also easily push values to the stack with the so called *stringmode*
With the command `"` you start the *stringmode* and with the same command `"` you can also end it again.
While the *stringmode* is active every Character the PC encounters will be pushed as an ASCII-Value to the stack.
The Program
```befunge
"abc" @
```
will leave the stack in the following state after completion:
```befunge
| 99 |
| 98 |
| 97 |
```
> **NOTE:**
>
> - A `"` starts and ends the stringmode
> - While in stringmode every Command will be pushed as its ASCII Value to the stack
### A real program
I know I promised you a Hello-World program. And we are nearly at the point where we can write it.
Only one last command is missing, the character-output (`,`) command. This command takes one value of the stack - interprets it as an ASCII-Value, and outputs it to wherever the interpreter wants to show the output.
So now - lets write this program:
```befunge
"dlroW olleH" ,,,,,,,,,,, @
```
![img02][2]
And that's it - a really simple Hello World. As you can see the program has two parts, first we fill the stack with the ASCII values for the two words "Hello World", and then we print each char to the output.
You may ask why Hello World is written backwards - that's because we work with a stack, so the last pushed value is the first popped value (LI-FO principle ). And - to output something in the correct order - we have to input it in the reversed order
Chapter 2: Let's golf
---------------------
### What we did wrong
Some of you probably know *Codegolf* . These are programming challenges with the target, to write program-code with the least amount of bytes.
Because of the limited amount of space in Befunge, this is here more important than ever. In our last example you saw how to output a string, but the longer the string gets the more commas you have to write and sooner or later you will have filled the whole space.
So now let's try to optimize our Hello World a little bit
> **NOTE:**
> *"Good code is small code"*
### Decision, Decisions, Decisions
One of the missing things is the possibility to do conditional logic. Our program has to react to something and react differently depending on the results.
So now it's time to introduce 2 new commands `|` and `_` . These commands are called "Decision making". They pop a value from the stack and change the PC direction depending on its value. The value is interpreted as an boolean, the conversion used is the same you probably know from C. If the value is Zero, its false - otherwise its true.
The `|` Command routes the PC to the top if the value is true and to the bottom if the value is false. Respective behaves the `_` command with left and right.
An other command is the `:`, what it does is pretty simple - it duplicates the top-stack value.
And the last command is `$`: It pops a value from the stack and does nothing with it.
With this knowledge we can now finally optimize our *Hello World*:
```befunge
v
v , <
> "dlroW olleH" > : |
> $ @
```
![img03][3]
So what happens here ?
- First we put "Hello World" reversed on the stack
- The we go in a loop, in every loop we do
- Duplicate the TOS[^tos]
- Test if the TOS is != 0 (true for every ASCII Character)
- Output the TOS
- This loop goes on until the stack is empty. When we now try to access the empty stack (with the duplicate command) it results in two zeros on the stack. This is so because an access to an empty stack will always result in a zero
- The returned zero is now interpreted as false and we exit the loop, the remaining zero on the stack is removed with the pop command and the program exits on the `@` command
> **NOTE:**
>
> - **_** and **|** change the PC direction depending on a value popped from the stack
> - **:** duplicates the top-of-stack-value
> - **$** removes the top-of-stack-value
> - Accessing an empty stack will result in a *zero*
### One step further
So our Hello World is now a lot smaller. Especially if you remove all the unneeded NOPs in the previous example.
So now lets go to one of the more interesting commands - **the trampoline** `#`.
After your PC encounters the trampoline it "jumps" over the next command, this results in a few very neat tricks you can do in your code.
The next Hello World I will show you is really optimized. This is the standard code to output a string of variable length and I don't believe that there is a more optimized way to do it.
```befunge
`"dlroW olleH">:#,_@
```
![img04][4]
Here you can see another neat feature of Befunge - the jump command is used twice, one time from left to right and the second time from right to left, so its used to skip two different commands (`:` and `,`) although only being one command.
Chapter 3: A little bit math
-------------------------------------
### Simple Calculus 101
Our programs are still rather static, we want them to actually do some work, for example calculate something.
Luckily Befunge has a few operational commands in its repertoire.
- `+` The *ADD* command: Pops two values from the stack and pushed the result of the addition
- `-` The *SUB* command: Pops two values from the stack and pushed the result of the subtraction
- `*` The *MULT* command: Pops two values from the stack and pushed the result of the multiplication
- `/` The *DIV* command: Pops two values from the stack and pushed the result of the division
- `%` The *MOD* command: Pops two values from the stack and pushed the result of the modulo operation
- `!` The *NOT* command: Pops one value from the stack, interprets it as a boolean and pushes the negation
- `´` The *COMP* command: Pops two values from the stack and pushed the result of the compare "a>b"
So if you want to calculate `4+5` just write
```befunge
45+
```
*speak:*
- Push 4 to stack
- Push 5 to stack
- Pop 4 and 5 -> Push (4+5=)9 to the stack
And if you want `(4+5)*6+7` you write
```befunge
45+6*7+
```
or
```befunge
4567+*+
```
Some of you may say that this notation seems familiar. That's true - it's the so called postfix[^pfix] notation.
This is one of Befunge's very neat "features" writing a mathematical expression will always result in a postfix notation and you can simply write a postfix notation in Befunge.
> **NOTE:**
>
> - +, *, - ,/, %, !, ` are the operator commands
> - These pop their arguments from the stack and push the result back
> - You can write mathematical expressions in postfix notation
### ... Eight, Nine, Ten ?
Perhaps you remember Chapter 2, we learned how to push digits to the stack. But probably you will one day feel the need to have values greater `9` on the stack. Now you can't just simply write `12` to push a twelve.
```befunge
12
```
would just push a `1` and a `2` on the stack.
The only option we have is two write a formula which results in a twelve on the stack, for example:
```befunge
66+
```
or
```befunge
62*
```
or
```befunge
93+
```
You see there are a lot of ways displaying the same value and we sure would like to know the best (= shortest) way to do so.
Now there are a few recipes how to do so:
#### Addition
The most simple way of displaying numbers is expressing them with addition.
So
```befunge
12 => 93+
19 => 991+
100 => 999999999991+
```
As you can see this method does not scale very well, and you will reach the acceptable limit pretty fast when the number becomes high enough.
#### Base-9
Base-9 is by far my favourite way of expressing numbers.
You probably know about different bases in number representation. `14` is `14` when written in base-10 (our everyday standard). But its also `D` in base-16 (or hexadecimal as you may know it). Or its `16` in base-8 (= octal), or its `1110` in base-2 (= binary).
Now we want to encode `105` (base-10).
`105` is `126` in base-9 and you can easily convert bases with this formula:
```befunge
(((1*9) + 2) * 9) + 6 => 105
```
or in Befunge (Postfix):
```befunge
69 29 1 *+ *+
```
or shorter:
```befunge
69291*+*+
```
Now you see why we use base-9, because we need to write the `9` in Befunge and `9` is the highest number we can push onto the stack in a single command.
This representation is nice because its really fast (and easy) to calculate the representation - even for big numbers, an because you can easily tell for every number you want how long its base-9-representation will be.
#### Factorization
Base-9 is nice in most cases, but often its not the optimal way. Factorization is often more compact.
The idea here is that you can split a number into its factors (and the factors should hopefully be `<10` ) and then multiply them.
So `196` `(=4*7*7)` becomes
```befunge
477**
```
Its said that this only works nice when you can factorize the number with factors smaller ten. Otherwise you would need to incorporate other mathematical operation to get to your result, finding the optimal combination here can be very CPU-intensive.
So for `107` a way of displaying it is:
```befunge
92+9*8+
```
#### Stringify
At last we can use a neat little trick. When we printed "Hello World", we put the ASCII values of the individual chars onto the stack. We can use that to express greater values. For example expressing `107` is as easy as `"k"`.
And for greater values you can even go on and write expressions based on the ASCII values of character:
```befunge
"~~)'"*++ (= 1851)
```
### Example
Number| Method | Code |Number| Method | Code |Number| Method | Code
------|----------------|----------------|------|----------------|----------------|------|----------------|----------------
0 | Boolean | `0` | 40 | Factorization | `58*` | 400 | Factorization | `25*5*8*`
1 | Boolean | `1` | 41 | Stringmode | `")"` | 401 | Base9 | `59894*+*+`
2 | Digit | `2` | 42 | Factorization | `67*` | 402 | Factorization | `79*4+6*`
3 | Digit | `3` | 43 | Stringmode | `"+"` | 403 | Base9 | `79894*+*+`
4 | Digit | `4` | 44 | Stringmode | `","` | 404 | Base9 | `89894*+*+`
5 | Digit | `5` | 45 | Factorization | `59*` | 405 | Factorization | `59*9*`
6 | Digit | `6` | 46 | Stringmode | `"."` | 406 | Base9 | `19095*+*+`
7 | Digit | `7` | 47 | Stringmode | `"/"` | 407 | Factorization | `59*9*2+`
8 | Digit | `8` | 48 | Factorization | `68*` | 408 | Factorization | `98+3*8*`
9 | Digit | `9` | 49 | Factorization | `77*` | 409 | Factorization | `59*9*4+`
10 | Factorization | `25*` | 50 | Stringmode | `"2"` | 410 | Base9 | `59095*+*+`
11 | Factorization | `92+` | 51 | Stringmode | `"3"` | 411 | Base9 | `69095*+*+`
12 | Factorization | `26*` | 52 | Stringmode | `"4"` | 412 | Base9 | `79095*+*+`
13 | Factorization | `94+` | 53 | Stringmode | `"5"` | 413 | Factorization | `69*5+7*`
14 | Factorization | `27*` | 54 | Factorization | `69*` | 414 | Base9 | `09195*+*+`
15 | Factorization | `35*` | 55 | Stringmode | `"7"` | 415 | Factorization | `99*2+5*`
16 | Factorization | `28*` | 56 | Factorization | `78*` | 416 | Factorization | `94+4*8*`
17 | Factorization | `98+` | 57 | Stringmode | `"9"` | 417 | Base9 | `39195*+*+`
18 | Factorization | `29*` | 58 | Stringmode | `":"` | 418 | Base9 | `49195*+*+`
19 | Base9 | `192*+` | 59 | Stringmode | `";"` | 419 | Base9 | `59195*+*+`
20 | Factorization | `45*` | 60 | Stringmode | `"<"` | 420 | Factorization | `25*6*7*`
21 | Factorization | `37*` | 61 | Stringmode | `"="` | 421 | Base9 | `79195*+*+`
22 | Base9 | `492*+` | 62 | Stringmode | `">"` | 422 | Base9 | `89195*+*+`
23 | Base9 | `592*+` | 63 | Factorization | `79*` | 423 | Factorization | `59*2+9*`
24 | Factorization | `38*` | 64 | Factorization | `88*` | 424 | Factorization | `59*8+8*`
25 | Factorization | `55*` | 65 | Stringmode | `"A"` | 425 | Factorization | `98+5*5*`
26 | Base9 | `892*+` | 66 | Stringmode | `"B"` | 426 | Factorization | `79*8+6*`
27 | Factorization | `39*` | 67 | Stringmode | `"C"` | 427 | Factorization | `69*7+7*`
28 | Factorization | `47*` | 68 | Stringmode | `"D"` | 428 | Base9 | `59295*+*+`
29 | Base9 | `293*+` | 69 | Stringmode | `"E"` | 429 | Base9 | `69295*+*+`
30 | Factorization | `56*` | 70 | Stringmode | `"F"` | 430 | Base9 | `79295*+*+`
31 | Base9 | `493*+` | 71 | Stringmode | `"G"` | 431 | Base9 | `89295*+*+`
32 | Factorization | `48*` | 72 | Factorization | `89*` | 432 | Factorization | `68*9*`
33 | Stringmode | `"!"` | 73 | Stringmode | `"I"` | 433 | Factorization | `68*9*1+`
34 | Base9 | `793*+` | 74 | Stringmode | `"J"` | 434 | Base9 | `29395*+*+`
35 | Factorization | `57*` | 75 | Stringmode | `"K"` | 435 | Base9 | `39395*+*+`
36 | Factorization | `49*` | 76 | Stringmode | `"L"` | 436 | Base9 | `49395*+*+`
37 | Stringmode | `"%"` | 77 | Stringmode | `"M"` | 437 | Factorization | `68*9*5+`
38 | Stringmode | `"&"` | 78 | Stringmode | `"N"` | 438 | Factorization | `89*1+6*`
39 | Stringmode | `"'"` | 79 | Stringmode | `"O"` | 439 | Factorization | `68*9*7+`
Chapter 4: The end
-----------------------------------
### Self modification
Befunge has one pretty big feature you haven't even seen now ... **self-modification**.
Yes you heard right, Befunge is capable of modifying its own code while running.
The responsible commands are `p` and `g`, namely *put* and *get*.
With *put* you can modify a specific command. *put* gets 3 Values from the stack `x`, `y` and `value`. `x` and `y` describe the position of the command and `value` is the new value of this field.
The *get* command works the other way around, it gets the value of a command at a specific position. *get* gets 2 values from the stack `x` and `y`, these values describe a specific field in the code, the command then gets the ASCII value of this command and pushed is onto the stack.
### A more complex example
Perhaps you want to try to solve the next task on your own. You already know everything you need and at the bottom of this tutorial you will find a full table of every command.
> Write a program that outputs the Fibonacci numbers, one after the other
> An example output would look like:
> `1,1,2,3,5,8 ...`
I write an simple solution that does this task. Note that I didn't highly optimize it on purpose, so its easier to follow what's actually going on:
```befunge
>> 100p 110p 1. ",", 1.",", > 00g 10g: 00p + :. ",", 10p v
^ <
```
![img05][5]
If you want an explanation what's going on here, I also a little explanation:
```
[0,0] is the first "variable cell" - it contains the second last value
[1,0] is the first "variable cell" - it contains the last value
100p Initialize cell [0,0] with '1'
110p Initialize cell [1,0] with '1'
1.",", Output '1,'
1.",", Output '1,'
> Loop Begin
00g Hole [0,0]
10g Hole [1,0]
: Duplicate [1,0]
00p Set cell [0,0] to the value of [1,0]
+ Add [0,0] + [1,0]
: Duplicate the result
.",", Output the result and a comma
10p Write the result in [1,0]
v Loop End
```
Note the cells `[0,0]` and `[0,1]` are used as temporary "variable" fields. We can put there values with the `p` command and later read them with the `g` command
### Command overview
Character | Name | Description
--------------------|---------|---------------------------------------------------------------------
`+` | ADD | Adds two values from the stack together and pushes the result back
`-` | SUB | Subtracts two values from the stack from each other and pushes the result back
`*` | MULT | Multiplies two values from the stack and pushes the result back
`/` | DIV | Divides two values from the stack and pushes the result back
`%` | MOD | Executes Modulo on two values from the stack and pushed teh result back
`!` | NOT | Gets a (boolean) value from the stack and pushes it negated back
`´` | GT | Pushes the result of a greater than over two value from the stack, to the stack
`^` | PCT | Set PC-Delta to *up*
`>` | PCL | Set PC-Delta to *left*
`v` | PCB | Set PC-Delta to *down*
`<` | PCR | Set PC-Delta to *right*
`?` | PCRAND | Set PC-Delta to a random direction
`#` | JMP | Jumps over the next command
`_` | IFH | A horizontal If
<code>&#124;</code> | IFV | A vertical If
`:` | DUP | Duplicates the TOS
`\` | SWAP | Swaps two values from the stack
`$` | POP | Removes the TOS
`.` | OUT-INT | Outputs the TOS as a number
`,` | OUT-ASC | Outputs the TOS as a character
`&` | IN-INT | Asks the user for a number and puts it on the stack
`~` | IN-ASC | Asks the user for a character and puts it on the stack
`p` | PUT | Sets a field to a specific value
`g` | GET | Gets the value of a field and pushes it onto the stack
`@` | STOP | Stops the program execution
`"` | STRMODE | Starts/Stops the stringmode
`0`-`9` | NUMBERS | Pushes the respective number onto the stack
[^tos]: TOS = top of stack
[^pfix]: Postfix = [Postfix notation][6]
[1]: http://i.imgur.com/Jkks7Uy.gif?1
[2]: http://i.imgur.com/Z5Ljr5Z.gif?1
[3]: http://i.imgur.com/82FKwkM.gif?1
[4]: http://i.imgur.com/AqpsPRW.gif?1
[5]: http://i.imgur.com/rxqZhIJ.gif?1
[6]: http://en.wikipedia.org/wiki/Reverse_Polish_notation

View File

@ -0,0 +1,33 @@
My BFJoust bot
---------------------
[Brainfuck Joust](http://esolangs.org/wiki/BF_Joust) is a tournament for Bots written in [brainfuck](http://esolangs.org/wiki/Brainfuck) *(or at least in a brainfuck-like language)*.
The board consist of 10 to 30 fields, one player starts left and one player right. The value of every field goes from `-127` to `128` and then wraps around, every field has a value of zero, except the two starting fields (the "flags"), they have a value of `128`.
Your goal is to zero the enemy flag for two consecutive cycles and you loose if you either leave the board or the enemy zeroes your flag first. Of course the bots are written in brainfuck, which adds a whole lot of interesting limitations due to brainfucks minimalistic (7) set of operations.
The thing that surprised me the most is the [strategically depth](http://esolangs.org/wiki/BF_Joust_strategies) of the game (despite the simple rules and language) and the fact that there are [extremely efficient and complex programs](http://codu.org/eso/bfjoust/in_egobot/) out there.
So here is my own bot *(originally made for [stackexchange](http://codegolf.stackexchange.com/questions/36645/brainfedbotsforbattling-a-brainf-tournament))*, it can't really keep up with the big ones from egojoust but I'm fairly proud of it:
```
{{CODE}}
```
A few notes to the BFJoust extensions to the brainfuck language:
`(a)*n` repeats the command `a` n-times.
`(a{b}c)%n` equals `(a)%n b (c)%n` but allows unmatched square-brackets in `a` or `b`.
So you see it's merely more than syntactic sugar, and probably more a precompiler than anything else.
But be aware, I personally made the experience in my js interpreter that treating is as a precompiler is not always a good idea. Because once you start expanding the code, the result can become pretty big pretty fast.
BFJoust with javascript
-------------------------------
I wrote a little [BFJoust script](https://maximum-sonata.codio.io/index.html) where you can let two bots visually fight against each other, but as I said above I did the mistake of expanding the code before running it, so this only works with programs that won't expand to an insanely large brainfuck code.
And at last a few words to the arena: In BFJoust there are 40 different settings, every board length two times. One time normal and one time with one bot inverted (`+` <-> `-`). This way you can eliminate luck and see which bot performs better.

View File

@ -0,0 +1,126 @@
.Net format specifier Cheat Sheet
============================
Here my (growing) collection of C# format specifier, this is not a complete list but rather a collection of specifier I needed in the past:
###Short syntax summary
```csharp
"{0,5:000;0.#;0}"
```
```plain
{
0 // The index of the displayed object
,5 // A padding of 5
000;0.#;0 // The format string
}
```
```plain
000 // The format for positive numbers (min 3 places, fill missing with zeroes)
;
0.# // The format for negative numbers (at least one digit before decimal point, maximum one after)
;
0 // The format for zeros (one digit - a zero)
```
###Align right with leading spaces
```csharp
"{0,6}" // min-width: 6 character
```
###Digits after decimal point
```csharp
"{0:0.00}" // exactly 2 digits after decimal point
"{0:0.##}" // maximal 2 digits after decimal point
```
###Align positive & negative numbers with leading zeros
```csharp
/* integer: */
"{0:0000;-000}" // min-width: 4 character
/* float: */
"{0:0000.00;-000.00}" // min-width: 4 character + 2 decimal places
```
###Remove minus sign
```csharp
"{0:0;0}"
```
###Show thousand seperator
```csharp
"{0:#,0}" // only for integer
```
###Scale numbers
```csharp
"{0:#,#}" // normal number
"{0:#,##0, kilo}" // (number/1000) kilo
"{0:#,##0,, mill}" // (number/1000/1000) million
"{0:#,##0,,, bill}" // (number/1000/1000/1000) billion
```
> **see: ** [stackoverflow.com](http://stackoverflow.com/questions/11731996/string-format-numbers-thousands-123k-millions-123m-billions-123b)
###Right align currency
```csharp
"{0,10:#,##0.00}" // Right aligned & always 2 decimal places
```
> **see: ** [stackoverflow.com](http://stackoverflow.com/questions/7422625/right-align-currency-in-string-format)
###Align strings
```csharp
"{0,-10}" // Left aligned, min-width: 10 chars
"{0,10}" // Right aligned, min-width: 10 chars
```
###Datetime formatting
```csharp
"s" // second
"m" // minute
"t" // AM/PM
"h" // hour (1-12)
"H" // hour (0-23)
"d" // day
"M" // month
"y" // year
"z" // timezone offset
"{0:D/M/yyyy}" // z.B.: 28/2/2013"
```
###Only show positive numbers
```csharp
"{0:#.#####;'';}"
```
###Add a prefix/suffix
```csharp
"{0:(0)}" // ( number )
"{0:_0_}" // _number_
```
###Format Hexadecimal
```csharp
"{0:X}" // z.B.: 138D5
"{0:x}" // z.B.: 138d5
"{0:X8}" // z.B.: 000138D5
"0x{0:X8}" // z.B.: 0x000138D5
```

View File

@ -0,0 +1,25 @@
Rapla Enhancement Script
=====================
![rapla_logo](/data/blog/Rapscript/logo.png)
I don't think many of you know [rapla](https://code.google.com/p/rapla/). And you should probably be thankful about that. But if you happen to study at the DHBW-KA, or any other place that uses rapla you could be interested in my enhancement-script.
Rapla is a Resource scheduling and event planing software (mostly used for timetables), and my rapscript enhances the *(not very good)* online view.
![full_preview](/data/blog/Rapscript/preview.png)
It contains the following features:
- Highlight courses you participate in
- Highlight special events (free days, etc. ^_^)
- Cleaner style
- Highlight current day
- Highlight current hour
- Show horizontal time marker, that moves in realtime (the red line)
- Show on weekends (saturday / sunday) the next week insted of the current
The script works on [Greasemonkey](https://addons.mozilla.org/de/firefox/addon/greasemonkey/) for Firefox or [Tampermonkey](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo) for Google Chrome and can be downloaded from this Gist:
[Open Gist](https://gist.github.com/Mikescher/f3d51a40dd0c5228df86)

View File

@ -0,0 +1,200 @@
A sudoku solver in Befunge-93
=======================
![sudoku debug](/data/blog/SudokuSolver/sudoku.png)
Because of [this project euler puzzle](https://www.mikescher.com/blog/1/Project_Euler_with_Befunge/problem-096) I spend the last few days implementing a sudoku solver in befunge-93 (as always I ignored the 80x25 size restriction because otherwise befunge-93 would be not turing-complete and I'm pretty sure this problem impossible).
There are two general types of sudoku problems, the one were in every step there is an cell with an obvious solution (these are the easy ones) and the ones were you have to guess sometimes (or deploy more complex strategies).
My solver is universal and can solve both ones. If there are no obvious cells it tries to make an smart guess and if the guess was wrong we backtrack and try with the next number.
Below i try to describe my general approach and a few caveats I stumbled across. You can look at the full up-to-date source code on [github](https://github.com/Mikescher/BefungePrograms).
~~~
v XX ########### ########### ############################# #############################
C C #36 2 89# # # # # # #
PPPPP # 361 # # # # # # #
XXX # # # # # # # #
LLLLLL #8 3 6 2# # # # # # #
PMMMM #4 6 3 7# # # # ########## # # #
MM MMM #6 7 1 8# # # # ################## # # #
XXX XX # # # # # ################## # # #
# 418 # # # # ####################### # # #
#97 3 14# # # # ####################### # # ####### #
########### ########### # #################### # # ############ #
# #################### # # ############### #
> 492*+11p9:*:9*61p>1-v # ################ # # ################# #
>9+\9/1+p:0\:9%11g+\9/1+p:| # ############ # # ################# #
^%9:\+*86%+88 g+1/9\+9%9::$#:< # ############ # # ######## ######## #
v1/*93\+*75%*93:\0:-1<g16p021< # ################ # # ################# #
>+p:0\:39*%89*+\39*/#| 1#:+# p# < # #################### # # ################# #
v p030$<>p152p::9%v # ####################### # # ############### #
>9:*>1-:9/32p:9%22p212^vg+1/9\+9< # ####################### # # ############ #
v10 $< >68*-:42pv # ####################### # # ####### #
4 ^_^#: <v!< # ################## # # #
p @ >1-:9%24p:9/3v^_ v # ########## # # #
>30g9:*-!#^_9:*^ 4$ # ########## # # #
v0p450_v#!-*86 g+g431+g429p< # # # #
> > :#^_$14g#v_015p v # # # #
^ p410< # # # #
p6 v:p45+1g44 < # # # #
4>4p9 >1-:44p57*24g3*44g3%+v # # # #
1 #>|:_v#g++/3g44*3g431+ < ############################# #############################
0 $ >64g1+64p ^| #
^ < $<v># #< >025p035p045p9:*:*55p9:*>1-: 9%16p:9/26p916gv
>64g:!#v_1-#^_14g1+14pv#> >42g68*+22g9+3 v ^ < v _v#!g54 $_^#!:<_v#<-*86g+g621+<
v $$<vg43p22g42p211<: v9p03+1g03p+1g2<^ p25g02< v02:+1 g 02<vg65$_ v0p650p640< ^1 <<
>32p54g42p20g5v- >1-:13p"X"57*22g3*13g3%++132g3*13g3v >p11g2 5 g+v! ! >1+:66p57*16g3*66g1-3%v
>>01-::17p27p37p9:*v_$ 9v 21 vg++/3g31*3g231++%3g31*3g22*98p++/< vp210p+g 5 31<5>pv < _v#g++/3-1g66*3g621++<
v94p76/9:p75%9:-1<^:<<: p v _52g89*22g3*13g3%v >25g22p3 5 gv 56 : - >56g1+56p4 v
>2*+57g+167g+g20g- |p: > ^:|:p++/3g31*3g231++< ^ p24g54p 2 3< g4 >9^|+!`g51g66!!g6< p
v*86g+g761+g759<7* >$9>1-:23p"X"57*23g3*42g1-3%++132g3*42g1-3v 5^g66 <> ^5
>-17p57g27 p67g3^* # vg++/3-1g24*3g231++%3-1g24*3g32*98p++/< >6g`!+#^_16g25p26g35p46g45p56g5^
v*98p76/*93:p75%*93:-1< _$ v>^v _52g89*23g3*42g1-3%v v ># ^#<
>57g+167g+g20g-! #v_:^< 2 >:|:p++/3-1g24*3g231++<
v75*750p+g761+g75*980< :: 0v19$<>p"X"57*22g3*42g1-3%+ + 133g3*42g1-3/++v
>167g3/+g 68*-!| g>-:33^vg++/3-1g24*3g331++% 3 -1g24*3g22*98p <
^+/3g759<v61+/ 3g759*86< 1^1 < v_52g89*22g3*42g1-3%v
>g+167g+p^>7g3/+p30g1-30p^ - |:< p++/3-1g24*3g331++<
vp51g71p+g731+g72+*2940p02 < >$9>1-::3%23p3/33p"X"57 * 22g3/3*23g+3*42g1-3%++132g3/3*33g+3*42gv
^ ># #<v > ^ v># #<^ vg++/3-1g24*3+g33* 3 /3g231++%3-1g24*3+g32*3/3g22*98p++/3-1 <
^ $_^#!:g21$< v :_52g89*22g3/3*23g+ 3 *42g1-3%v
^># #<v^ _^#: p++/3-1g24*3+g33* 3 /3g231++<
^># #< ^
~~~
(To test the code I recommend my [befunge interpreter BefunExec](https://github.com/Mikescher/BefunExec), simply input your sudoku in the top-left rectangle and run the program)
Introduction
============
First we need a data structure for the current state, we simply take a `9x9` array
(a sudoku puzzle contains nine `areas` with nine `fields` each = 9x9 fields in total).
A zero in a field means `unkown` and the numbers one to nine are valid values.
Because I wanted the program too look a little bit aesthetically pleasing
I used ASCII-numbers instead of raw binary values ;).
This array is from now on called `grid`.
Next we need a place to remember which values are possible for a specific array.
Therefore we use a 3x3 array for each field which fields represent the
nine possible numbers (`1..9`).
This data is structured into a `27x27` field (`= (9*3)x(9*3)`).
This array is from now on called `pspace`
Set Up
======
Initially we take the known values from our input and write them to the `grid` array.
For each value we also have to update the `pspace`.
If we set a value in the grid to `v` at position `[x|y]` we have to
- set the `pspace[dx, dy, v]` of all fields `dx|dy` in the same area to true
- set the `pspace[dx, dy, v]` of all fields `dx|dy` in the same row to true
- set the `pspace[dx, dy, v]` of all fields `dx|dy` in the same column to true
We can calculate the position in the `pspace` are like this:
~~~
px = (x*3) + ((v - 1)%3)
py = (y*3) + ((v - 1)/3)
~~~
Solving simple puzzles
======================
After we initialized the array we scan all fields and search
for pspace configurations where all numbers are set to `1` except one.
For these it is unambiguous which value it has (the one where `pspace== `)
Then we set this value in the `grid` array (and - again - update the `pspace`).
This step is repeated until the whole puzzle is solved.
(We need a isSolved function for this, but it only needs to check
for zeroes in the `grid` array).
Solving the rest
================
The described approach works great for the first puzzle, but as soon
as we try the second one we stumble upon a problem.
There a situation where not a single field has a obvious solution and we
need to "guess" a value to continue. (And then backtrack if the guess
was wrong and try the next possibility).
For this we introduce two new arrays into our memory model `rstack[27,27]` and `rlatter[9,9]`.
And we keep track of a global value that we call `recursionDepth` (initially `1`).
Every time we set a value in our `pspace` we write into
the corresponding `rstack` field our current `recursionDepth`.
Now as soon as we encounter a dead end (all fields are either solved or have multiple possible solutions)
we go one step down.
First we increase the `recursionDepth` by 1.
Then we take the easiest field (field with the least possible values) and set it to the lowest possible value.
We also set the corresponding field in `rlatter` to the current `recursionDepth`.
Now we continue with our algorithm as before until one of three things happen:
1. We solve all fields. Then we found the solution and our program terminates.
2. We have again the situation with no unambiguous fields, then we do the whole thing again and increase the `recursionDepth` again.
3. We end up with a field which `pspace` contains only ones (meaning there is no possible value for this field).
In case 3 it is obvious that we must have guessed wrong in the past.
So first we undo the last recursion step.
We iterate through the whole `rstack` and everywhere where the value equals our current `recursionDepth`
we set the corr corresponding `pspace` value to zero.
If the value in `grid` at this position is not zero we set it to zero
(because we must have set it in this recursion level and now we can't be sure if its correct).
Then we decrease the `recursionDepth` by one again.
Before we reseted all the values we looked into our `rlatter` to get the grid position that
was guessed when we entered this `recursionDepth`
(This is simply done by searching the field with the value `recursionDepth`).
Now we "guess" the next possible value for this field and increase the recursion
depth once again (and continue with our normal operations).
If we can't find another value for this field (means we tried all available and all where wrong)
we simply go one step further up (decrease the `recursionDepth` again, complete with undoing all changes etc)
and take the next value of this guess.
This algorithms runs until we either find a solution or there is no solution
(in this case we would after a while try to undo the `recursionDepth` 1 and go into `recursionDepth` ).
Design decisions
================
This algorithm is definitely not the best in a normal C-like language but it has a its pros in befunge.
First we have realized recursion-like behavior without a stack or an data structure with unknown size
(which would happen if we simply wrote the "stack" to our befunge-grid).
It is true that we could possibly be used the normal befunge-stack for our recursion-stack.
But we do enough stuff already on the stack that this would result in a much more complex program
(and at times a really big stack).
My solution with an additional grid (`rstack`) is imho quite elegant and has a fixed size from the beginning on.
I didn't optimize this program as much as possible. Not only because its quite a big program but also because it
doesn't need to be terribly fast and because I wanted to keep the interesting
look with thee four array fields while solving.
In the end this is not my fastest solution but a complete sudoku solver in befunge is in my opinion quite cool.
As with most bigger befunge programs I made a reference Implementation in C# (with LinqPad),
if someone wants to better understand this algorithm, perhaps also look at this code :D
Befunge Modules
===============
In my reference C# implementation I have a few methods that get called multiple times from different origins.
But (through carefully writing the code) there is never a case where the same method is multiple times on the call stack
(so the program is not recursive, even though the algorithm is).
I didn't want to duplicate code, especially not some of the bigger methods so I used a special cell to remember the "return value".
After the method has finished it then can decide on its own where the PC has to flow to resume the program.
One example is the method `SetValueAndHints`. This method can get called from three different positions:
- In the initialization phase
- When `RecursionStepDown` fails and we discard the current guess
- When a cell contains invalid data and we discard the current guess
The return address is part of the "parameter-package" that gets written every time `SetValueAndHints` is called
(The package contains `returnAddr`, `cx`, `cy`, `value`, `recDepth`).
Because *(see above)* the method is never two times on our theoretical "call stack" we don't have problems with someone overriding
values there while they are still used.
For the method `RecursionStepDown` we got lucky. Even though the method is called from two different positions we could rearrange
our algorithm in a way that the return address of both calls is the same,
that means we don't need a return address (but still a parameter package).
The same is true for `RecursionStepUp`, it is called from two sources but both time it returns into a call of `RecursionStepDown`.

View File

@ -14,5 +14,5 @@ return
'download' => 'direkt',
],
'long_description' => function(){ return file_get_contents(__DIR__ . '/All in One_description.md'); },
'thumbnail_url' => '/images/program_thumbnails/All in One.png',
'thumbnail_url' => 'All in One.png',
];

View File

@ -15,5 +15,5 @@ return
'download' => 'direkt',
],
'long_description' => function(){ return file_get_contents(__DIR__ . '/Beepster_description.md'); },
'thumbnail_url' => '/images/program_thumbnails/Beepster.png',
'thumbnail_url' => 'Beepster.png',
];

View File

@ -15,5 +15,5 @@ return
'wiki' => 'https://github.com/Mikescher/BefunUtils/wiki',
],
'long_description' => function(){ return file_get_contents(__DIR__ . '/BefunUtils_description.md'); },
'thumbnail_url' => '/images/program_thumbnails/BefunUtils.png',
'thumbnail_url' => 'BefunUtils.png',
];

View File

@ -14,5 +14,5 @@ return
'download' => 'direkt',
],
'long_description' => function(){ return file_get_contents(__DIR__ . '/BefunZ_description.md'); },
'thumbnail_url' => '/images/program_thumbnails/BefunZ.png',
'thumbnail_url' => 'BefunZ.png',
];

View File

@ -14,5 +14,5 @@ return
'download' => 'direkt',
],
'long_description' => function(){ return file_get_contents(__DIR__ . '/Blitzer_description.md'); },
'thumbnail_url' => '/images/program_thumbnails/Blitzer.png',
'thumbnail_url' => 'Blitzer.png',
];

View File

@ -15,5 +15,5 @@ return
'homepage' => 'http://borderlinedefense.99k.org/',
],
'long_description' => function(){ return file_get_contents(__DIR__ . '/Borderline Defense_description.md'); },
'thumbnail_url' => '/images/program_thumbnails/Borderline Defense.png',
'thumbnail_url' => 'Borderline Defense.png',
];

View File

@ -14,5 +14,5 @@ return
'download' => 'direkt',
],
'long_description' => function(){ return file_get_contents(__DIR__ . '/Crystal Grid_description.md'); },
'thumbnail_url' => '/images/program_thumbnails/Crystal Grid.png',
'thumbnail_url' => 'Crystal Grid.png',
];

View File

@ -14,5 +14,5 @@ return
'download' => 'direkt',
],
'long_description' => function(){ return file_get_contents(__DIR__ . '/Deal or no Deal_description.md'); },
'thumbnail_url' => '/images/program_thumbnails/Deal or no Deal.png',
'thumbnail_url' => 'Deal or no Deal.png',
];

View File

@ -14,5 +14,5 @@ return
'download' => 'direkt',
],
'long_description' => function(){ return file_get_contents(__DIR__ . '/Dynamic Link Fighters_description.md'); },
'thumbnail_url' => '/images/program_thumbnails/Dynamic Link Fighters.png',
'thumbnail_url' => 'Dynamic Link Fighters.png',
];

View File

@ -14,5 +14,5 @@ return
'download' => 'https://github.com/Mikescher/extendedGitGraph/',
],
'long_description' => function(){ return file_get_contents(__DIR__ . '/ExtendedGitGraph_description.md'); },
'thumbnail_url' => '/images/program_thumbnails/ExtendedGitGraph.png',
'thumbnail_url' => 'ExtendedGitGraph.png',
];

View File

@ -14,5 +14,5 @@ return
'download' => 'direkt',
],
'long_description' => function(){ return file_get_contents(__DIR__ . '/Graveyard of Numbers_description.md'); },
'thumbnail_url' => '/images/program_thumbnails/Graveyard of Numbers.png',
'thumbnail_url' => 'Graveyard of Numbers.png',
];

View File

@ -15,5 +15,5 @@ return
'download' => 'direkt',
],
'long_description' => function(){ return file_get_contents(__DIR__ . '/H2O_description.md'); },
'thumbnail_url' => '/images/program_thumbnails/H2O.png',
'thumbnail_url' => 'H2O.png',
];

View File

@ -15,5 +15,5 @@ return
'github' => 'https://github.com/Mikescher/HexSolver',
],
'long_description' => function(){ return file_get_contents(__DIR__ . '/HexSolver_description.md'); },
'thumbnail_url' => '/images/program_thumbnails/HexSolver.png',
'thumbnail_url' => 'HexSolver.png',
];

View File

@ -14,5 +14,5 @@ return
'download' => 'direkt',
],
'long_description' => function(){ return file_get_contents(__DIR__ . '/Infinity Tournament_description.md'); },
'thumbnail_url' => '/images/program_thumbnails/Infinity Tournament.png',
'thumbnail_url' => 'Infinity Tournament.png',
];

View File

@ -14,5 +14,5 @@ return
[
],
'long_description' => function(){ return file_get_contents(__DIR__ . '/Keygen Dancer_description.md'); },
'thumbnail_url' => '/images/program_thumbnails/Keygen Dancer.png',
'thumbnail_url' => 'Keygen Dancer.png',
];

View File

@ -14,5 +14,5 @@ return
'download' => 'direkt',
],
'long_description' => function(){ return file_get_contents(__DIR__ . '/LAN-Control_description.md'); },
'thumbnail_url' => '/images/program_thumbnails/LAN-Control.png',
'thumbnail_url' => 'LAN-Control.png',
];

View File

@ -14,5 +14,5 @@ return
'download' => 'direkt',
],
'long_description' => function(){ return file_get_contents(__DIR__ . '/LightShow_description.md'); },
'thumbnail_url' => '/images/program_thumbnails/LightShow.png',
'thumbnail_url' => 'LightShow.png',
];

Some files were not shown because too many files have changed in this diff Show More