53
\$\begingroup\$

Yesterday, I stumbled on a very clever thing.

minitech's tic-tac-toe profile game

Yes, that's a working implementation of Tic-Tac-Toe on a user profile page, from @minitech. Of course, the moment I saw it, I had to reverse engineer his idea and one-up him :P

mellamokb's towers of hanoi profile game

Here's my own example embedded directly in the post. It's a tad buggy due to a couple of implementation details I haven't worked out a good solution for. Sometimes after you click a peg, it doesn't update properly until another page refresh:

Towers of Hanoi

Reset

Can you do better?

  • Create a working game in your posted answer (or your user profile page). This is done via appropriately configuring a webserver you own (or writing a program that acts as a webserver), and embedding content from it in a post, using the referer to determine what commands the user is giving to the game.
  • Coolest idea (most votes) wins the competition, by Canada Day (Sunday, July 1, 2012 @ 11:59 PM EST)
  • In the event of a tie, the older answer wins.
\$\endgroup\$
18
  • 1
    \$\begingroup\$ +1 Simple, but brilliant idea! BTW - for the deadline I think you mean June 2, 2012. \$\endgroup\$ May 23, 2012 at 15:22
  • \$\begingroup\$ Short deadline + already answered your own question = downvote. \$\endgroup\$
    – boothby
    May 23, 2012 at 18:50
  • 1
    \$\begingroup\$ @boothby: I was actually thinking about deleting my answer. The intention was to provide a concrete example, not to win the contest (or votes, I don't greatly care about rep). Can you provide some constructive suggestions to the competition? What would you like the deadline to be? How should the spec be changed to motivate you to participate? \$\endgroup\$
    – mellamokb
    May 23, 2012 at 19:02
  • 4
    \$\begingroup\$ I just noticed that minitech's AI can't play a perfect game of tic-tac-toe. Play center, bottom-left, top-center, center-right, center-left. \$\endgroup\$
    – PhiNotPi
    May 23, 2012 at 23:04
  • 1
    \$\begingroup\$ @Mr.Wizard: working fine on FF 12.0 & Windows 7 here, could you post more details about what's not working? \$\endgroup\$ Jun 4, 2012 at 13:36

5 Answers 5

36
\$\begingroup\$

C# - Stack Exchange Hangman

Guess names of Stack Exchange websites in this Hangman game:


(source: apphb.com)


`A` `B` `C` `D` `E` `F` `G` `H` `I` `J` `K` `L` `M` `N` `O` `P` `Q` `R` `S` `T` `U` `V` `W` `X` `Y` `Z`
`New game`

This was done using ASP.NET MVC 3.0. Here's the code of the Controller that does the trick:

public class HangmanController : Controller
{
    public ActionResult Index()
    {
        var game = Session["hangman"] as HangmanGame ?? HangmanGame.New();

        game = ExecuteGameCommand(game);

        Session["hangman"] = game;

        var imageRenderer = new HangmanImageRenderer(game);
        return new ImageResult(imageRenderer.Render());
    }

    private HangmanGame ExecuteGameCommand(HangmanGame game)
    {
        var referrerQuery = Request.UrlReferrer != null ? Request.UrlReferrer.Query : string.Empty;

        if (referrerQuery.Contains("_new_hangman_"))
            return HangmanGame.New();

        if(game.IsOver())
            return game;

        var chosenLetter = HangmanGame.ValidLetters
            .FirstOrDefault(letter => referrerQuery.Contains(String.Format("_hangman_{0}_", letter)));

        if (chosenLetter != default(char))
            game.RegisterGuess(chosenLetter);

        return game;
    }
}

Other than this code, there are three more classes that I haven't included since they are pretty long and straightforward:

  • HangmanGame - here's where the game business rules are implemented
  • HangmanImageRenderer - the class that encapsulates all the GDI ugliness
  • ImageResult - a custom ActionResult that is used to return a dynamically generated image

The entire code listing is available at http://pastebin.com/ccwZLknX

\$\endgroup\$
7
  • \$\begingroup\$ +1 Wow, you guys are awesome :). Like the ideas so far! \$\endgroup\$
    – mellamokb
    May 24, 2012 at 20:24
  • \$\begingroup\$ Cool, never heard of appharbor.com. Are you really paying to host your answer? \$\endgroup\$
    – mellamokb
    May 24, 2012 at 20:25
  • \$\begingroup\$ @mellamokb no, I'm using the Appharbor's free hosting plan. If this gets clicked a lot I guess I'll have to pay though... :) \$\endgroup\$ May 24, 2012 at 20:29
  • 2
    \$\begingroup\$ I was going to mention if necessary, I can provide custom subdomain hosting and FTP access to my hosting site. \$\endgroup\$
    – mellamokb
    May 24, 2012 at 20:32
  • \$\begingroup\$ @mellamokb thanks, but I think this hosting will do. I was just joking about many clicks. :) \$\endgroup\$ May 25, 2012 at 6:34
28
\$\begingroup\$

Conway's Game of Life

+1 generation - +5 generations - zoom in - zoom out

Load pattern: random - glider - gunstar - snail - lwss - lightspeedoscillator1 - tumbler

Used Python and SVG output. I have tried using single pixels at first (so you could toggle single cells), but it did not work out, because the browser does not load images in order. Also, much bigger patterns are possible like this without crashing my webserver.

Update:

I had some fun with python and added several features and improvements:

  • Added HUD with population count, zoom and name
  • Patterns in the rle format can now be loaded (long list, via) using the pattern parameter (e.g. ?pattern=glider). The filesize is limited to 1.5kB
  • Can forward n generations, limited to 5 at a time, using the next parameter
  • Slightly improved algorithm. It's not really fast though, I want this to stay simple
  • It also works standalone now (uses either referer or its own query string): https://copy.sh/fcgi-bin/life2.py?pattern=gosperglidergun


sessions = {}

WIDTH = 130
HEIGHT = 130
RULE = (3,), (2, 3)

def read_pattern(filename, offset_x, offset_y):

    filename = PATH + filename + '.rle.gz'

    try:
        if os.stat(filename).st_size > 1500:
            return ['pattern too big', set()]
    except OSError as e:
        return ['could not find pattern', set()]

    file = gzip.open(filename)

    x, y = offset_x, offset_y
    name = ''
    pattern_string = ''
    field = []

    for line in file:
        if line[0:2] == '#N':
            name = line[2:-1]
        elif line[0] != '#' and line[0] != 'x':
            pattern_string += line[:-1]

    for count, chr in re.findall('(\d*)(b|o|\$|!)', pattern_string):
        count = int(count) if count else 1

        if chr == 'o':
            for i in range(x, x + count):
                field.append( (i, y) )
            x += count
        elif chr == 'b':
            x += count
        elif chr == '$':
            y += count
            x = offset_x
        elif chr == '!':
            break

    file.close()

    return [name, set(field)]



def next_generation(field, n):

    for _ in range(n):

        map = {}

        for (x, y) in field:
            for (i, j) in ( (x-1, y-1), (x, y-1), (x+1, y-1), (x-1, y), (x+1, y), (x-1, y+1), (x, y+1), (x+1, y+1) ):
                map[i, j] = map[i, j] + 1 if (i, j) in map else 1

        field = [
            (x, y)
            for x in range(0, WIDTH)
            for y in range(0, HEIGHT)
            if (x, y) in map
            if ( (map[x, y] in RULE[1]) if (x, y) in field else (map[x, y] in RULE[0]) )
        ]

    return field


def life(env, start):


    if 'REMOTE_ADDR' in env:
        client_ip = env['REMOTE_ADDR']
    else:
        client_ip = '0'

    if not client_ip in sessions:
        sessions[client_ip] = read_pattern('trueperiod22gun', 10, 10) + [2]

    session = sessions[client_ip]

    if 'HTTP_REFERER' in env:
        query = urlparse.parse_qs(urlparse.urlparse(env['HTTP_REFERER']).query, True)
    elif 'QUERY_STRING' in env:
        query = urlparse.parse_qs(env['QUERY_STRING'], True)
    else:
        query = None

    timing = time.time()

    if query:
        if 'next' in query:
            try:
                count = min(5, int(query['next'][0]))
            except ValueError as e:
                count = 1
            session[1] = set( next_generation(session[1], count) )
        elif 'random' in query:
            session[0:2] = 'random', set([ (random.randint(0, WIDTH), random.randint(0, HEIGHT)) for _ in range(800) ])
        elif 'pattern' in query:
            filename = query['pattern'][0]
            if filename.isalnum():
                session[0:2] = read_pattern(filename, 10, 10)
        elif 'zoomin' in query:
            session[2] += 1
        elif 'zoomout' in query and session[2] > 1:
            session[2] -= 1

    timing = time.time() - timing

    start('200 Here you go', [
        ('Content-Type', 'image/svg+xml'), 
        ('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'), 
        ('Expires', 'Tue, 01 Jan 2000 12:12:12 GMT')
    ])

    pattern_name, field, zoom = session

    yield '<?xml version="1.0" encoding="UTF-8"?>'
    yield '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'
    yield '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="400px" height="200px">'
    yield '<!-- finished in %f -->' % timing
    yield '<text x="0" y="10" style="font-size:10px">Population: %d</text>' % len(field)
    yield '<text x="100" y="10" style="font-size:10px">Zoom: %d</text>' % zoom
    yield '<text x="180" y="10" style="font-size:10px; font-weight:700">%s</text>' % pattern_name
    yield '<line x1="0" y1="15" x2="666" y2="15" style="stroke:#000; stroke-width:1px" />'

    for (x, y) in field:
        yield '<rect x="%d" y="%d" width="%d" height="%d"/>' % (zoom * x, zoom * y + 20, zoom, zoom)

    yield '</svg>'


from flup.server.fcgi import WSGIServer
import random
import re
import gzip
import os
import urlparse
import time

WSGIServer(life).run()

You can take my code as a template for further python fastcgi submissions.

\$\endgroup\$
8
  • \$\begingroup\$ +1 Awesome! One suggestion: append #5946 to your links, and it will jump back to your post after every update. \$\endgroup\$
    – mellamokb
    May 23, 2012 at 20:11
  • \$\begingroup\$ hmm.. at least it worked when i tried it.. ah. because in Towers of Hanoi you're always clicking on different pegs. hmm \$\endgroup\$
    – mellamokb
    May 23, 2012 at 20:12
  • \$\begingroup\$ @mellamokb it works, but you can't click the same link twice now \$\endgroup\$
    – copy
    May 23, 2012 at 20:13
  • \$\begingroup\$ Ya, I just realized that lol. Guess you can provide a disclaimer that when doing next generation, just press F5 for future iterations instead of clicking the next link again and again after the first time. \$\endgroup\$
    – mellamokb
    May 23, 2012 at 20:13
  • 1
    \$\begingroup\$ @mellamokb thanks. In my opinion you don't really need to accept answers on this platform, because it looks as if the challenge is closed \$\endgroup\$
    – copy
    Jul 2, 2012 at 19:09
21
\$\begingroup\$

Clojoban! [WIP]

I wanted to make a bigger game out of this to learn Clojure, so this took a while to pull off (and got pretty big.) I've had a lot of fun doing it, btw!

Clojoban! Restart levelNew game

. .

- No-op*

. .

*(click here if game becomes unresponsive)

Instructions

You are Robby Robby, a hard-working robot. You work at a FlipCo Industries as a heavy load carrier. Your job is to move each box A box to a goal A goal spending as few steps as possible. FlipCo's facilities are DANGEROUS. There are lots of challenges and special places to discover.

If you get stuck, click Restart level (but your step count won't be reset!)


You can also play at Clojoban's front page (although it kind of ruins the purpose of the challenge.) It fixes the infamous anchor problem, doesn't require cross-site cookies and you can play with your keyboard arrow keys! You can also play at my user profile page without the annoying anchor problem.

In Firefox the image doesn't flicker while it's loading so it's a bit more comfortable to play.

This game is FAR from completion, Clojoban is still a work in progress. You can see the complete sourcecode at Clojoban's GitHub project page. There's some info in the README about contributing. I need levels too! See the level format at the example levels. You can peek at Clojoban's issue tracker and see what's coming next!

\$\endgroup\$
12
  • \$\begingroup\$ Now your reputation is 11 :) \$\endgroup\$
    – mellamokb
    Jun 16, 2012 at 14:04
  • \$\begingroup\$ @mellamokb thanks! Game is embedded now :) \$\endgroup\$ Jun 16, 2012 at 16:23
  • \$\begingroup\$ Apparently this didn't get much attention. Any tips for improving? \$\endgroup\$ Jun 21, 2012 at 15:41
  • \$\begingroup\$ Your answer is good, I think this question overall has stagnated. I haven't seen much activity or votes for the last couple of days. \$\endgroup\$
    – mellamokb
    Jun 21, 2012 at 17:27
  • \$\begingroup\$ That's a great game! I think you should make a standalone version; I got to the third level and got rid of pressing the No-op button. :) Anyway, great work! \$\endgroup\$ Jun 23, 2012 at 10:48
17
\$\begingroup\$

Maze


(source: t15.org)


New - Noop button

I started from the PHP maze generator I found here: http://dev.horemag.net/2008/03/01/php-maze-generation-class/.

EDIT: changed the output to PNG instead of SVG (for better cross browser compatibility).

EDIT 2: added a header for fixing IE cookie compatibility. Should now work correctly in all major browsers.

The image does not refresh if you take the same direction twice (due to the anchor links). Press F5 the second time, or play the maze on my stackoverflow profile.

EDIT 3: Added a no-op button for easily being able to take the same direction twice (see comments below).

<?php
// based upon the maze generator by Evgeni Vasilev (PHP Adaptation)
// see http://dev.horemag.net/2008/03/01/php-maze-generation-class/
class Maze
{
  var $maze = array();
  var $mx = 0;
  var $my = 0;
  var $xplayer = 1;
  var $yplayer = 1;

  function Maze($mx, $my)
  {
    $mx +=2;
    $my +=2;
    $this->mx = $mx;
    $this->my = $my;
    $dx = array( 0, 0, -1, 1 );
    $dy = array( -1, 1, 0, 0 );
    $todo = array(); 
    $todonum = 0;

    for ($x = 0; $x < $mx; ++$x){
      for ($y = 0; $y < $my; ++$y){
        if ($x == 0 || $x == $mx-1 || $y == 0 || $y == $my-1) {
          $this->maze[$x][$y] = 32;
        } else {
          $this->maze[$x][$y] = 63;
        }
      }
    }
    $x = rand(1, $mx-2); $y = rand(1, $my-2);
    $x = 1; $y = 1;
    $this->maze[$x][$y] &= ~48;
    for ($d = 0; $d < 4; ++$d){
      if (($this->maze[$x + $dx[$d]][$y + $dy[$d]] & 16) != 0) {
        $todo[$todonum++] = (($x + $dx[$d]) << 16) | ($y + $dy[$d]);
        $this->maze[$x + $dx[$d]][$y + $dy[$d]] &= ~16;
      }
    }

    while ($todonum > 0) {
      $n = rand(0, $todonum-1);
      $x = $todo[$n] >> 16;
      $y = $todo[$n] & 65535;
      $todo[$n] = $todo[--$todonum];
      do {
        $d = rand(0, 3);
      } while (($this->maze[$x + $dx[$d]][$y + $dy[$d]] & 32) != 0);
      $this->maze[$x][$y] &= ~((1 << $d) | 32);
      $this->maze[$x + $dx[$d]][$y + $dy[$d]] &= ~(1 << ($d ^ 1));
      for ($d = 0; $d < 4; ++$d){
        if (($this->maze[$x + $dx[$d]][$y + $dy[$d]] & 16) != 0) {
          $todo[$todonum++] = (($x + $dx[$d]) << 16) | ($y + $dy[$d]);
          $this->maze[$x + $dx[$d]][$y + $dy[$d]] &= ~16;
        }
      }
    }
    $this->maze[1][1] &= ~1;
    $this->maze[$mx-2][$my-2] &= ~2;
  }

  function _drawLine($img,$color, $x1, $y1, $x2, $y2)
  {
    imageline($img, $x1, $y1, $x2, $y2, $color);
  }
  
  function _drawPlayer($img, $x, $y, $r, $colorborder, $colorfill)
  {
    imagefilledellipse($img, $x, $y, $r, $r, $colorfill);
    imageellipse($img, $x, $y, $r, $r, $colorborder);
  }
  
  function _drawWin($img, $color)
  {
    imagestring($img, 5, 170, 90, "YOU WIN!", $color);
  }
  
  function movePlayerDown()
  {
    if ($this->yplayer+1 < $this->my-1 && ($this->maze[$this->xplayer][$this->yplayer] & 2) == 0)
    $this->yplayer++;
  }

  function movePlayerUp()
  {
    if ($this->yplayer-1 > 0 && ($this->maze[$this->xplayer][$this->yplayer] & 1) == 0)
      $this->yplayer--;
  }

  function movePlayerRight()
  {
    if ($this->xplayer+1 < $this->mx-1 && ($this->maze[$this->xplayer][$this->yplayer] & 8) == 0)
      $this->xplayer++;
  }  

  function movePlayerLeft()
  {
    if ($this->xplayer-1 > 0 && ($this->maze[$this->xplayer][$this->yplayer] & 4) == 0)
      $this->xplayer--;
  }  
  
  function renderImage($xs, $ys)
  {
    $off = 0;
    $w = ($this->mx*$xs)+($off*2); $h = ($this->my*$ys)+($off*2);
    $img = imagecreatetruecolor($w, $h);
    imagesetthickness($img, 2);
    $fg = imagecolorallocate($img, 0, 0, 0);
    $bg = imagecolorallocate($img, 248, 248, 248);
    $red = imagecolorallocate($img, 255, 0, 0);
    imagefill($img, 0, 0, $bg);
    if (($this->xplayer == $this->mx-2) && ($this->yplayer == $this->my-2)) {
      $this->_drawWin($img, $red);
      return $img;
    }
    
    for ($y = 1; $y < $this->my-1; ++$y) {
      for ($x = 1; $x < $this->mx-1; ++$x){
        if (($this->maze[$x][$y] & 1) != 0)
          $this->_drawLine ($img, $fg, $x * $xs + $off, $y * $ys + $off, $x * $xs + $xs + $off, $y * $ys + $off);
        if (($this->maze[$x][$y] & 2) != 0)
          $this->_drawLine ($img, $fg, $x * $xs + $off, $y * $ys + $ys + $off, $x * $xs + $xs + $off, $y * $ys + $ys + $off);
        if (($this->maze[$x][$y] & 4) != 0)
          $this->_drawLine ($img, $fg, $x * $xs + $off, $y * $ys + $off, $x * $xs + $off, $y * $ys + $ys + $off);
        if (($this->maze[$x][$y] & 8) != 0)
          $this->_drawLine ($img, $fg, $x * $xs + $xs + $off, $y * $ys + $off, $x * $xs + $xs + $off, $y * $ys + $ys + $off);
        if ($x == $this->xplayer && $y == $this->yplayer) {
          $this->_drawPlayer ($img, $x * $xs + ($xs/2), $y * $ys + ($ys/2), 14, $fg, $red);
        }
      }
    }
    return $img;
  }
}
header('P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"');
session_start();
$orig_url = $_SERVER['HTTP_REFERER'];
if (!isset($_SESSION['maze']) || strpos($orig_url, 'resetmaze')){
    $_SESSION['maze'] = new Maze(25,10);
}
$maze = $_SESSION['maze'];
if (strpos($orig_url, 'playerdown')) { $maze->movePlayerDown(); }
if (strpos($orig_url, 'playerup')) { $maze->movePlayerUp(); }
if (strpos($orig_url, 'playerright')) { $maze->movePlayerRight(); }
if (strpos($orig_url, 'playerleft')) { $maze->movePlayerLeft(); }
$img = $maze->renderImage(16,16);
header("Content-Type: image/png");
imagepng($img);
imagedestroy($img);
?>
\$\endgroup\$
11
  • 1
    \$\begingroup\$ +1 Nice! for a better experience, add #answer-6171 at the end of your links. Otherwise, no one will have enough patience to solve the maze. \$\endgroup\$ Jun 2, 2012 at 20:56
  • \$\begingroup\$ @W0lf: Thanks. I thought about including the # links but the problem is they don't refresh the page when you take the same direction twice (which can happen in a maze ;-). I've added them now so people will have to press F5 the second time they want to take the same direction. Another option is to play it here (my SO profile: stackoverflow.com/users/81179/christophed) \$\endgroup\$ Jun 2, 2012 at 21:16
  • \$\begingroup\$ I would fancy a simple no-operation link (refresh?) to make the update easier when trying to move twice in the same direction :) \$\endgroup\$
    – kaoD
    Jun 4, 2012 at 17:04
  • \$\begingroup\$ @kaoD: Without the anchor parts (#) which jump to the right answer to the question (internally, without page refresh), a simple page refresh would work fine (as you can see on my linked profile where the same maze is also available). But the problem then would be that you would find yourself at the top of the page after every refresh. The real problem is that we're really limited in what we can include in an answer here on StackOverflow (for good reason off course), we can't use arbitrary Javascript for example. I have no idea about an easy way out. \$\endgroup\$ Jun 4, 2012 at 17:32
  • \$\begingroup\$ You can still have the anchor and it will go straight to your post, but with a different URL (which will allow for correct gameplay.) I find the F5 method clunky. \$\endgroup\$
    – kaoD
    Jun 4, 2012 at 17:50
11
\$\begingroup\$

"Simon says" game

Unfortunately, I could not get this submission in on time by the (somewhat arbitrary) deadline, but I really wanted to demonstrate animation in such a user profile game, and none of the previous submissions are animated. This game is a clone of the classic Milton Bradley game Simon, in which the player tries to repeat an increasingly long sequence of signals.

Information about this game, including its source code, is available at its GitHub page. There may be occasional graphical glitches (especially on Windows computers) arising from the hackish "palette animation" that avoids the need for a graphics drawing library. The existence of these glitches can serve as a useful excuse for quickly losing this game because of terrible memory.

Additionally, the effects of high latency and limited bandwidth can make this game much more challenging than the original as well. I think that in order to get much more than five points (when the game first speeds up), you will need to determine which light flashes one more time than in the previous round rather than depending on the correct sequence (which is very hard to do).

If this game fails to work for you (it restarts every time you click a button), your browser might be blocking its cookie. I have not yet added a workaround, so for the time being, please either use Chrome, Opera, or Firefox or temporarily change your Internet Explorer or Safari cookie settings.

Edit 2018-05-24: At this time, I have deleted the publicly accessible Heroku instance of this app. I may or may not put the app back online at a later date. The app's code is still available on GitHub, so you can either run it locally or create your own Heroku app instance if you wish to play the game.

\$\endgroup\$
1
  • \$\begingroup\$ +1 That is absolutely brilliant! Never thought of doing dynamically generated animate gifs :P \$\endgroup\$
    – mellamokb
    Jul 2, 2012 at 15:43

Not the answer you're looking for? Browse other questions tagged or ask your own question.