Refactris

This post is in lieu of writing a proper one, either on the generic maths operators which Marc Gravell has been hard at work on, or on C# 4 which I have a number of opinions about (no surprise there). I will write about both of those topics, but I really ought to do some more work on the manuscript for chapter 2 of the book before I go to bed. Posting a blog entry is a reward for finishing indexing chapters 2 and 13, but both of the serious posts will take longer than I really have time for right now.

So, Refactris. This is a silly idea born a couple of weeks ago, at work. You see, several months ago, I had run out of work on a Friday afternoon at 4pm. My then-team-leader, Rohan, foolishly challenged me to write console-mode Tetris in an hour. I had great fun, and had a demonstrable game of Tetris working precisely one hour later. Now combine that with refactoring, one of the ideas of which is that you can remove lines of code by finding code duplication etc. Put the two together, and you get Refactris.

Letters, digits and symbols would fall from the top, and whenever they landed (in a normal Tetris manner) the game would try to compile the code in the bucket, finding as much to compile as possible. It would try the top line, then the top two lines, then the top three lines, etc, until it reached the bottom – then try the second line, the second and third lines together, etc. Code which compiled would be removed.

Clearly it’s a stupid idea, and I haven’t actually tried to implement it or anything silly like that. Funny enough to share though, and if any of you wish to give it a go, I’d love to see the results.

5 thoughts on “Refactris”

  1. That’d be great. Not interested in top quality code, I have trouble finding free time for hobby coding and would like to see the kind of thing to aim for if I can find a free hour. :)

  2. Gaaah! Look what you made me do: I wrote one in an hour too. Bunch of hideous and possibly misquoted code follows:

    —8< ---
    using System;
    using System.Collections.Generic;
    using System.Threading;

    namespace CTetris
    {
    class BlockShape
    {
    int _width;
    int _height;
    bool[,] _shape;
    int _left;
    int _top;
    char _ch;

    static BlockShape[] _shapes;
    static string CharChoice = "%$#*&@XO~";

    private BlockShape(params string[] shape)
    {
    _height = shape.Length;
    _width = shape[0].Length;
    _shape = new bool[_width, _height];
    _ch = CharChoice[Program.R.Next(CharChoice.Length)];

    for (int y = 0; y < _height; ++y)
    {
    for (int x = 0; x < _width; ++x)
    _shape[x, y] = shape[y][x] != ' ';
    }
    }

    private BlockShape(BlockShape prototype)
    {
    _left = prototype._left;
    _top = prototype._top;
    _width = prototype._width;
    _height = prototype._height;
    _ch = prototype._ch;
    _shape = new bool[_width, _height];
    Array.Copy(prototype._shape, _shape, _width * _height);
    }

    public BlockShape Clone()
    {
    return new BlockShape(this);
    }

    public char Ch
    {
    get { return _ch; }
    }

    public int Width
    {
    get { return _width; }
    }

    public int Height
    {
    get { return _height; }
    }

    public int Top
    {
    get { return _top; }
    set { _top = value; }
    }

    public int Left
    {
    get { return _left; }
    set { _left = value; }
    }

    public bool this[int x, int y]
    {
    get
    {
    if (x < 0 || x >= Width)
    return false;
    if (y < 0 || y >= Height)
    return false;
    return _shape[x, y];
    }
    }

    public void Rotate(int clockwise)
    {
    if (clockwise == 0)
    return;
    while (clockwise < 0)
    clockwise += 4;
    while (clockwise > 1)
    {
    Rotate(1);
    –clockwise;
    }

    int newWidth = _height;
    int newHeight = _width;
    bool[,] newShape = new bool[newWidth, newHeight];
    for (int y = 0; y < _height; ++y)
    for (int x = 0; x < _width; ++x)
    {
    // width' = height
    // height' = width
    // x' = width' - 1 - y
    // y' = x

    newShape[newWidth - 1 - y, x] = _shape[x, y];
    }

    _shape = newShape;
    _width = newWidth;
    _height = newHeight;
    }

    static BlockShape()
    {
    List shapes = new List();

    shapes.AddRange(new[] {
    new BlockShape(“****”),

    new BlockShape(“** “,
    ” **”),

    new BlockShape(” **”,
    “** “),

    new BlockShape(” * “,
    “***”),

    new BlockShape(“**”,
    “**”),

    new BlockShape(“* “,
    “***”),

    new BlockShape(” *”,
    “***”)
    });

    _shapes = shapes.ToArray();
    }

    public static BlockShape GetRandomBlock()
    {
    int index = Program.R.Next(_shapes.Length);
    int rot = Program.R.Next(4);
    BlockShape result = _shapes[index].Clone();
    result.Rotate(rot);
    return result;
    }
    }

    class Screen
    {
    bool[,] _blocks;
    int _width;
    int _height;

    public Screen(int width, int height)
    {
    _blocks = new bool[width, height];
    _width = width;
    _height = height;
    }

    public int Width
    {
    get { return _width; }
    }

    public int Height
    {
    get { return _height; }
    }

    // Top line is line 0 i.e. y = 0.
    // Bottom line is line Height – 1.
    public bool this[int x, int y]
    {
    get
    {
    if (y < 0)
    return false;
    if (x < 0 || x >= Width)
    return true;
    if (y >= Height)
    return true;
    return _blocks[x, y];
    }
    set
    {
    if (y < 0)
    return;
    if (x < 0 || x >= Width)
    return;
    if (y >= Height)
    return;
    _blocks[x, y] = value;
    }
    }

    // Return true if a conflict with blocks on screen.
    public bool HitTest(BlockShape block)
    {
    for (int x = 0; x < block.Width; ++x)
    for (int y = 0; y < block.Height; ++y)
    {
    if (this[block.Left + x, block.Top + y] && block[x, y])
    return true;
    }
    return false;
    }

    // Add all blocks of block to the screen.
    public void AddBlock(BlockShape block)
    {
    for (int x = 0; x < block.Width; ++x)
    for (int y = 0; y < block.Height; ++y)
    this[x + block.Left, y + block.Top] |= block[x, y];
    }

    // Remove a line from the screen, moving everything above down.
    public void RemoveLine(int line)
    {
    for (int y = line; y >= 0; –y)
    for (int x = 0; x < Width; ++x)
    this[x, y] = this[x, y - 1];
    }

    private bool IsLineComplete(int line)
    {
    for (int x = 0; x < Width; ++x)
    if (!this[x, line])
    return false;
    return true;
    }

    // Yields on each line found - opportunity for visual feedback.
    public IEnumerable LineCheck()
    {
    for (int y = Height – 1; y >= 0; –y)
    if (IsLineComplete(y))
    {
    RemoveLine(y);
    yield return y;
    }
    }

    public void Draw(int x, int y, BlockShape currentBlock)
    {
    // currentBlock may be null for redrawing while line-checking.
    Console.CursorLeft = x;
    Console.CursorTop = y;

    for (int cy = -1; cy < = Height; ++cy)
    {
    for (int cx = -1; cx <= Width; ++cx)
    {
    Console.SetCursorPosition(x + cx + 1, y + cy + 1);

    char ch = ' ';

    if (this[cx, cy])
    ch = '8';

    if (currentBlock != null)
    {
    int bx = cx - currentBlock.Left;
    int by = cy - currentBlock.Top;
    if (currentBlock[bx, by])
    ch = currentBlock.Ch;
    }

    Console.Write(ch);
    }
    }
    }
    }

    static class Program
    {
    public readonly static Random R = new Random();

    static void Main()
    {
    Screen screen = new Screen(10, 20);
    BlockShape currentBlock = BlockShape.GetRandomBlock();
    currentBlock.Left = (screen.Width - currentBlock.Width) / 2;

    for (;;)
    {
    screen.Draw(5, 1, currentBlock);
    Thread.Sleep(300);

    // User input.
    while (Console.KeyAvailable)
    {
    BlockShape nextBlock = currentBlock.Clone();
    ConsoleKeyInfo info = Console.ReadKey(false);
    switch (info.Key)
    {
    case ConsoleKey.LeftArrow:
    nextBlock.Left -= 1;
    break;

    case ConsoleKey.RightArrow:
    nextBlock.Left += 1;
    break;

    case ConsoleKey.UpArrow:
    nextBlock.Rotate(1);
    break;
    }

    if (!screen.HitTest(nextBlock))
    {
    currentBlock = nextBlock;
    screen.Draw(10, 10, currentBlock);
    }
    }

    // Try to move down.
    BlockShape movedDown = currentBlock.Clone();
    movedDown.Top += 1;
    if (!screen.HitTest(movedDown))
    {
    // We're fine to move down; do that, then next iteration.
    currentBlock = movedDown;
    continue;
    }

    // We've reached the end of the block's life.
    // Add to screen, remove completed lines, then
    // try to add a new block at the top.
    // If we can't add at the top, then end of game.

    screen.AddBlock(currentBlock);
    screen.Draw(10, 10, null);
    Thread.Sleep(50);
    foreach (int line in screen.LineCheck())
    {
    screen.Draw(10, 10, null);
    Thread.Sleep(100);
    }

    currentBlock = BlockShape.GetRandomBlock();
    currentBlock.Left = (screen.Width - currentBlock.Width) / 2;
    if (screen.HitTest(currentBlock))
    break;
    }

    Console.WriteLine("Game over.");
    }
    }
    }
    --->8—

  3. Your game idea reminds me of Micro Mouse Goes Debugging, a home computer game from 1983 where you had to replace the symbols that monsters stole from a BASIC program listing.

Comments are closed.