Java 4k competition 2011

Finally the time a year when you are encouraged to write really dirty code, Java 4k competition! Well, maybe not dirty but certainly ugly.

The Java 4K competition is a game programmer competition. The goal of the competition is to create the best game possible in Java. But what's the catch? Well, it wouldn't be fun if there wasn't any catch! There are limitations, and these limitations are: The maximum allowed size of a game is only 4K

This year i forgot to exclude the manifest before i started with the size optimizations. I felt like a child on christmas when i discovered it! Good times :) Anyway, my game is a classic puzzle game where you control a rolling cube. The objective is to clear all colored bricks on the playfield by rolling over the bricks with the a side of the cube which have the same color as the brick. The game can be found on the Java 4k site and I encourage you to try all submitted games, some of them are really good.

Because Riven's excellent Compile 'n Shrink service seems to be down, probably due to the hacking of java-gaming.org, I'll post my own compression scripts here.

This is the script I use for this tedious task:
#!/bin/bash

if [ $# -ne 1 ]
then
  echo "Usage: `basename $0` "
  exit 65
fi

function printSize() {
   SIZE=$(ls -l $1 | awk '//{print $5}')
   printf "+ %-15s %s bytes [%d]\\n" $1 ${SIZE} $((SIZE-4096))
}

BIN=bin
LOG=../size.log

PRO_FLAGS="-libraryjars /lib/rt.jar -basedirectory ."

CLASS=$1.class
JAR=$1.jar
PACK=$1.pack.gz
PRO=$1.pro.jar
PRO_PACK=$1.pro.pack.gz
PRO_PACK_RAW=$1.pro.pack
PRO_PACK_7ZIP=$1.pro.pack.7.gz
PRO_PACK_K=$1.pro.pack.kzip
PRO_PACK_K_GZ=$1.pro.pack.k.gz

pushd .
cd $BIN

echo

#echo Generating manifest file
#echo -e Main-Class: $1\\n > $1.manifest

echo Running jar on $CLASS
jar cfM $JAR $CLASS

echo Running Proguard on $JAR
proguard $PRO_FLAGS -injars $JAR -outjars $PRO -keep public class $1

echo Running pack200 on $JAR
pack200 -G $PACK $JAR

echo Running pack200 on $PRO
pack200 -G $PRO_PACK $PRO
pack200 --no-gzip $PRO_PACK_RAW $PRO

echo Compressing $PRO_PACK_RAW with 7zip
7z a -mx=9 -tgzip $PRO_PACK_7ZIP $PRO_PACK_RAW > /dev/null

echo Compressing $PRO_PACK_RAW with Kzip
kzip /q /y /s0 /rn /b64 $PRO_PACK_K $PRO_PACK_RAW 
zip2gzip $PRO_PACK_K $PRO_PACK_K_GZ

echo
echo File size summary:
printSize $CLASS
printSize $JAR
printSize $PRO
printSize $PACK
printSize $PRO_PACK
printSize $PRO_PACK_7ZIP
printSize $PRO_PACK_K_GZ
echo

# Write to the log file
echo "----------------------------------" >> $LOG
date +"%Y-%m-%d %H:%M:%S" >> $LOG
echo "----------------------------------" >> $LOG
echo "File size summary:" >> $LOG
printSize $CLASS >> $LOG
printSize $JAR >> $LOG
printSize $PRO >> $LOG
printSize $PACK >> $LOG
printSize $PRO_PACK >> $LOG
printSize $PRO_PACK_7ZIP >> $LOG
printSize $PRO_PACK_K_GZ >> $LOG
echo >> $LOG

popd >> /dev/null
For even smaller files, replace the kzip command with a script that runs kzip 10-100 times and takes the smallest resulting file. You will also need pjt33's Zip2Gzip converter to convert the KZip files to GZip:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

// Based on http://www.gzip.org/zlib/rfc-gzip.html
// and http://www.pkware.com/documents/casestudies/APPNOTE.TXT
public class Zip2Gzip {
 // Usage: java Zip2Gzip [file.zip [file.gzip]]
 public static void main(final String[] args) throws IOException {
  InputStream in = new FileInputStream(args[0]);
  in = new BufferedInputStream(in);
  OutputStream out = new FileOutputStream(args[1]);
  out = new BufferedOutputStream(out);

  // Start by writing a gzip header.
  // Magic.
  out.write(0x1f);
  out.write(0x8b);
  // Compression method: inflate.
  out.write(0x08);
  // Flags: we include no optional extras.
  out.write(0x00);
  // Timestamp: unavailable.
  out.write(0x00);
  out.write(0x00);
  out.write(0x00);
  out.write(0x00);
  // Extra flags: none.
  out.write(0x00);
  // OS: unknown.
  out.write(0xff);

  // The next block of output is the compressed data. We need to process
  // the zip file header to find it and know how long it is.
  // local file header signature 4 bytes (0x04034b50)
  // version needed to extract 2 bytes
  // general purpose bit flag 2 bytes
  // compression method 2 bytes
  // last mod file time 2 bytes
  // last mod file date 2 bytes
  // Total so far: 14 bytes
  for (int i = 0; i < 14; i++) {
   in.read();
  }
  // crc-32 4 bytes
  final int crc1 = in.read();
  final int crc2 = in.read();
  final int crc3 = in.read();
  final int crc4 = in.read();
  // compressed size 4 bytes
  int cmpSz = (in.read() & 0xff) + ((in.read() & 0xff) << 8) + ((in.read() & 0xff) << 16)
    + ((in.read() & 0xff) << 24);
  // uncompressed size 4 bytes
  final int ucmpSz1 = in.read();
  final int ucmpSz2 = in.read();
  final int ucmpSz3 = in.read();
  final int ucmpSz4 = in.read();
  // file name length 2 bytes
  final int nameLen = (in.read() & 0xff) + ((in.read() & 0xff) << 8);
  // extra field length 2 bytes
  final int xfLen = (in.read() & 0xff) + ((in.read() & 0xff) << 8);
  // file name (variable size)
  for (int i = 0; i < nameLen; i++) {
   in.read();
  }
  // extra field (variable size)
  for (int i = 0; i < xfLen; i++) {
   in.read();
  }

  // Data follows, so we can copy it to the output.
  final byte[] buf = new byte[4096];
  while (cmpSz > 0) {
   final int desired = cmpSz > buf.length ? buf.length : cmpSz;
   final int len = in.read(buf, 0, desired);
   if (len == 0) {
    throw new EOFException();
   }
   out.write(buf, 0, len);
   cmpSz -= len;
  }

  // The output still needs the CRC32 and the uncompressed size.
  out.write(crc1);
  out.write(crc2);
  out.write(crc3);
  out.write(crc4);
  out.write(ucmpSz1);
  out.write(ucmpSz2);
  out.write(ucmpSz3);
  out.write(ucmpSz4);

  // Done. Be tidy.
  out.close();
  in.close();
 }
}

1 comment:

Birger said...

Najs, Var inte Markus Persson(Minecraft snubben) i tävligen också?