Create a User-Profile Mini-Game

C# - Stack Exchange Hangman

Guess names of Stack Exchange websites in this Hangman game:



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


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.


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!