Несколько наиболее показательных проектов для вашего решения. [[Некоторые из них могут стать примерами этой книги, а так же исчезнуть отсюда.]]

Крысы и лабиринты

Сначала создайте Черную доску (здесь должна быть ссылка на сайт), которая является объектом, в который кто угодно может записывать информацию. Эта обычная черная доска рисует лабиринт и используется в качестве информации о структуре лабиринта, приходящей обратно от крыс, которые исследуют его.

Теперь создайте сам лабиринт. Как и в реальном лабиринте, этот объект показывает очень мало информации о себе - получая координаты, он может сказать вам в каком месте стена или проход по четырем направлениям в ближайшем окружении этой координаты, и не более того. Для начинающих, прочтите лабиринт из текстового файла, но сначала найдите в Интернете алгоритм генерации лабиринта. В любом случае, в результате должен получится объект, которому передаются координаты лабиринта, и который сообщает о стенах и проходах вокруг этой координаты. Также вы должны быть способны спросить его относительно точки входа в лабиринт.

И, наконец, создайте класс Крысы исследующей лабиринт. Каждая крыса может общаться с черной доской, получая текущую информацию о координатах, и с лабиринтом, запрашивая новую информацию, основываясь на текущем положении крысы. Однако, каждый раз, когда крыса достигает разветвления, она создает новую крысу, чтобы каждая из них шла по своей ветке. Каждая крыса ограничена своей собственной ниткой (процессом). Когда крыса достигает тупика, она умирает, сообщив результат своего исследования черной доске.

Цель работы - получить полную карту лабиринта, но вы сначала должны также определить будет ли условием окончания реальное прохождение лабиринта или черная доска должна быть ответственна за решение.

Вот вам пример реализации от Джереми Майера (Jeremy Meyer):

//: projects:Maze.java
package projects;

import java.util.*;

import java.io.*;

import java.awt.*;

public class Maze extends Canvas {
  
private Vector lines; // a line is a char array
  
private int width = -1;
  
private int height = -1;
  
  
public static void main(String[] args) throws IOException {
     
if (args.length < 1) {
        
System.out.println("Enter filename");
         System.exit
(0);
     
}
     
Maze m = new Maze();
      m.load
(args[0]);
      Frame f =
new Frame();
      f.setSize
(m.width * 20, m.height * 20);
      f.add
(m);
      Rat r =
new Rat(m, 0, 0);
      f.setVisible
(true);
  
}
  
  
public Maze() {
     
lines = new Vector();
      setBackground
(Color.lightGray);
  
}
  
  
synchronized public boolean isEmptyXY(int x, int y) {
     
if (x < 0)
        
x += width;
     
if (y < 0)
        
y += height;
     
// Use mod arithmetic to bring rat in line:
     
byte[] by = (byte[]) (lines.elementAt(y % height));
     
return by[x % width] == ' ';
  
}
  
  
synchronized public void setXY(int x, int y, byte newByte) {
     
if (x < 0)
        
x += width;
     
if (y < 0)
        
y += height;
     
byte[] by = (byte[]) (lines.elementAt(y % height));
      by
[x % width] = newByte;
      repaint
();
  
}
  
  
public void load(String filename) throws IOException {
     
String currentLine = null;
      BufferedReader br =
new BufferedReader(new FileReader(filename));
     
for (currentLine = br.readLine(); currentLine != null; currentLine = br
            .readLine
()) {
        
lines.addElement(currentLine.getBytes());
        
if (width < 0 || currentLine.getBytes().length > width)
           
width = currentLine.getBytes().length;
     
}
     
height = lines.size();
      br.close
();
  
}
  
  
public void update(Graphics g) {
     
paint(g);
  
}
  
  
public void paint(Graphics g) {
     
int canvasHeight = this.getBounds().height;
     
int canvasWidth = this.getBounds().width;
     
if (height < 1 || width < 1)
        
return; // nothing to do
     
int width = ((byte[]) (lines.elementAt(0))).length;
     
for (int y = 0; y < lines.size(); y++) {
        
byte[] b;
         b =
(byte[]) (lines.elementAt(y));
        
for (int x = 0; x < width; x++) {
           
switch (b[x]) {
           
case ' ': // empty part of maze
              
g.setColor(Color.lightGray);
               g.fillRect
(x * (canvasWidth / width), y
                     *
(canvasHeight / height), canvasWidth / width,
                     canvasHeight / height
);
              
break;
           
case '*': // a wall
              
g.setColor(Color.darkGray);
               g.fillRect
(x * (canvasWidth / width), y
                     *
(canvasHeight / height),
                    
(canvasWidth / width) - 1,
                    
(canvasHeight / height) - 1);
              
break;
           
default: // must be rat
              
g.setColor(Color.red);
               g.fillOval
(x * (canvasWidth / width), y
                     *
(canvasHeight / height), canvasWidth / width,
                     canvasHeight / height
);
              
break;
           
}
         }
      }
   }
}
// /:~
//: projects:Rat.java
package projects;

public class Rat {
  
static int ratCount = 0;
  
private Maze prison;
  
private int vertDir = 0;
  
private int horizDir = 0;
  
private int x, y;
  
private int myRatNo = 0;
  
  
public Rat(Maze maze, int xStart, int yStart) {
     
myRatNo = ratCount++;
      System.out.println
("Rat no." + myRatNo + " ready to scurry.");
      prison = maze;
      x = xStart;
      y = yStart;
      prison.setXY
(x, y, (byte) 'R');
     
new Thread() {
        
public void run() {
           
scurry();
        
}
      }
.start();
  
}
  
  
public void scurry() {
     
// Try and maintain direction if possible.
      // Horizontal backward
     
boolean ratCanMove = true;
     
while (ratCanMove) {
        
ratCanMove = false;
        
// South
        
if (prison.isEmptyXY(x, y + 1)) {
           
vertDir = 1;
            horizDir =
0;
            ratCanMove =
true;
        
}
        
// North
        
if (prison.isEmptyXY(x, y - 1))
           
if (ratCanMove)
              
new Rat(prison, x, y - 1);
           
// Rat can move already, so give
            // this choice to the next rat.
           
else {
              
vertDir = -1;
               horizDir =
0;
               ratCanMove =
true;
           
}
        
// West
        
if (prison.isEmptyXY(x - 1, y))
           
if (ratCanMove)
              
new Rat(prison, x - 1, y);
           
// Rat can move already, so give
            // this choice to the next rat.
           
else {
              
vertDir = 0;
               horizDir = -
1;
               ratCanMove =
true;
           
}
        
// East
        
if (prison.isEmptyXY(x + 1, y))
           
if (ratCanMove)
              
new Rat(prison, x + 1, y);
           
// Rat can move already, so give
            // this choice to the next rat.
           
else {
              
vertDir = 0;
               horizDir =
1;
               ratCanMove =
true;
           
}
        
if (ratCanMove) { // Move original rat.
           
x += horizDir;
            y += vertDir;
            prison.setXY
(x, y, (byte) 'R');
        
} // If not then the rat will die.
        
try {
           
Thread.sleep(2000);
        
}
        
catch (InterruptedException e) {
           
throw new RuntimeException(e);
        
}
      }
     
System.out.println("Rat no." + myRatNo
            +
" can't move..dying..aarrgggh.");
  
}
}
// /:~

Файл инициализации лабиринта:

//:! projects:Amaze.txt
  
* **      *  * **      *
***    * *******    * ****
     ***          ***     
*****   **********   *****
* * * * **  ** * * * **  *
   * * *  * **  * * *  * **
*     **     *     **    
   * **   * **  * **   * **
*** *  *** ***** *  *** **
*      *   * *      *   *
   * ** * *     * ** * *  
///:~

Другие ресурсы относительно лабиринтов

Обсуждение алгоритмов создания лабиринтов и их реализации на Java (с исходниками):

http://www.mazeworks.com/mazegen/mazegen.htm

Обсуждение алгоритмов обнаружения коллизий и других индивидуальных/групповых перемещений для автономных физических объектов:

http://www.red3d.com/cwr/steer/

XML Декоратор

Напишите пару декораторов для I/O Читателей и Писателей, которые кодируют (для декоратора Писателя) и раскодируют (для декоратора Читателя) XML.

Инструменты

Содержит инструменты, необходимые для построения книги и т.п. Некоторые из них могут временны и исчезнут, когда база кода переместится в CVS.

Расширение Ant

Ant поставляется с дополнительным API, так что вы можете создать вашу собственную задачу, написав его на Java. Вы можете найти полное описание в официальной документации ANT и в опубликованных книгах по Ant.

В качестве альтернативы вы можете просто писать Java программы и вызывать ее из Ant. Этот способ не требует изучения дополнительного API. Например, чтобы скомпилировать код в этой книги, нам нужно проверить версию Java, чтобы пользователь использовал Java 1.3 или более позднюю, так что мы можем создать следующую программу:

//: com:bruceeckel:tools:CheckVersion.java
// {RunByHand}
package com.bruceeckel.tools;

public class CheckVersion {
  
public static void main(String[] args) {
     
String version = System.getProperty("java.version");
     
char minor = version.charAt(2);
     
char point = version.charAt(4);
     
if (minor < '3' || point < '0')
        
throw new RuntimeException("JDK 1.3.0 or higher "
              
+ "is required to run the examples in this book.");
      System.out.println
("JDK version " + version + " found");
  
}
}
// /:~

Здесь просто используется System.getProperty( ) для определения версии Java, и выбрасывается исключение, если она ниже, чем 1.3. Когда Ant обнаруживает исключение, он останавливает. Теперь вы можете включить следующий текст в любой файл построения, в котором вы хотите проверять номер версии:

<java
  taskname=
"CheckVersion"
 
classname="com.bruceeckel.tools.CheckVersion"
 
classpath="${basedir}"
 
fork="true"
 
failonerror="true"
/>

Если вы используете этот подход для добавления инструментов, вы можете написать его и быстро проверить его, и если это оправдано, вы можете затратить дополнительные силы и написать дополнительные расширения Ant.

Утилиты для массива

Не смотря на свою полезность, класс Arrays с недоделанной функциональностью. Например, было бы прекрасно иметь возможность легкой распечатки элементов массива без дополнительного регулярного ручного кодирования циклов loop. И как вы увидите, метод fill( ) просто принимает единственное значение и помещает его в массив, так что если вы хотите, например, заполнить массив случайно сгенерированными числами, метод filд( ) не поможет.

Таким образом, имеет смысл дополнить класс Arrays некоторыми дополнительными утилитами, которые будут помещены в пакет com.bruceeckel.util для удобства. В ней распечатывается массив любого типа и заполняется массив значениями или объектами, которые создаются генератором, который вы определяете.

Поскольку код должен быть создан для каждого примитивного типа так же как и для Object, то здесь будет много дублирующего кода. [14] Например, интерфейс "генератора" требуется для каждого типа, поскольку возвращаемый тип метода next( ) должен отличатся в каждом случае:

package com.bruceeckel.util;

//: com:bruceeckel:util:Generator.java
public interface Generator { Object next(); } // /:~

// : com:bruceeckel:util:BooleanGenerator.java
public interface BooleanGenerator { boolean next(); } // /:~

// : com:bruceeckel:util:ByteGenerator.java
public interface ByteGenerator { byte next(); } // /:~

// : com:bruceeckel:util:CharGenerator.java
public interface CharGenerator { char next(); } // /:~

// : com:bruceeckel:util:ShortGenerator.java
public interface ShortGenerator { short next(); } // /:~

// : com:bruceeckel:util:IntGenerator.java
public interface IntGenerator { int next(); } // /:~

// : com:bruceeckel:util:LongGenerator.java
public interface LongGenerator { long next(); } // /:~

// : com:bruceeckel:util:FloatGenerator.java
public interface FloatGenerator { float next(); } // /:~

// : com:bruceeckel:util:DoubleGenerator.java
public interface DoubleGenerator { double next(); } // /:~

Arrays2 содержит вариант метода toString( ), перегруженный для каждого типа. Эти методы позволяют вам легко распечатать массыв. Код toString( ) вводит использование объекта StringBuffer вместо String. Это уклон в сторону эффективности. Когда вы собираете строку в методе, которая может вызываться очень много раз, благоразумнее использовать более эффективный класс StringBuffer вместо более последовательных операций класса String. В этом примере StringBuffer создается с начальным значением, а String используются для добавления. В итоге, result приводится к типу String, так как это возвращаемое значение:

//: com:bruceeckel:util:Arrays2.java
// A supplement to java.util.Arrays, to provide additional
// useful functionality when working with arrays. Allows
// any array to be converted to a String, and to be filled
// via a user-defined "generator" object.
package com.bruceeckel.util;

import java.util.*;

public class Arrays2 {
  
public static String toString(boolean[] a) {
     
StringBuffer result = new StringBuffer("[");
     
for (int i = 0; i < a.length; i++) {
        
result.append(a[i]);
        
if (i < a.length - 1)
           
result.append(", ");
     
}
     
result.append("]");
     
return result.toString();
  
}
  
  
public static String toString(byte[] a) {
     
StringBuffer result = new StringBuffer("[");
     
for (int i = 0; i < a.length; i++) {
        
result.append(a[i]);
        
if (i < a.length - 1)
           
result.append(", ");
     
}
     
result.append("]");
     
return result.toString();
  
}
  
  
public static String toString(char[] a) {
     
StringBuffer result = new StringBuffer("[");
     
for (int i = 0; i < a.length; i++) {
        
result.append(a[i]);
        
if (i < a.length - 1)
           
result.append(", ");
     
}
     
result.append("]");
     
return result.toString();
  
}
  
  
public static String toString(short[] a) {
     
StringBuffer result = new StringBuffer("[");
     
for (int i = 0; i < a.length; i++) {
        
result.append(a[i]);
        
if (i < a.length - 1)
           
result.append(", ");
     
}
     
result.append("]");
     
return result.toString();
  
}
  
  
public static String toString(int[] a) {
     
StringBuffer result = new StringBuffer("[");
     
for (int i = 0; i < a.length; i++) {
        
result.append(a[i]);
        
if (i < a.length - 1)
           
result.append(", ");
     
}
     
result.append("]");
     
return result.toString();
  
}
  
  
public static String toString(long[] a) {
     
StringBuffer result = new StringBuffer("[");
     
for (int i = 0; i < a.length; i++) {
        
result.append(a[i]);
        
if (i < a.length - 1)
           
result.append(", ");
     
}
     
result.append("]");
     
return result.toString();
  
}
  
  
public static String toString(float[] a) {
     
StringBuffer result = new StringBuffer("[");
     
for (int i = 0; i < a.length; i++) {
        
result.append(a[i]);
        
if (i < a.length - 1)
           
result.append(", ");
     
}
     
result.append("]");
     
return result.toString();
  
}
  
  
public static String toString(double[] a) {
     
StringBuffer result = new StringBuffer("[");
     
for (int i = 0; i < a.length; i++) {
        
result.append(a[i]);
        
if (i < a.length - 1)
           
result.append(", ");
     
}
     
result.append("]");
     
return result.toString();
  
}
  
  
// Fill an array using a generator:
  
public static void fill(Object[] a, Generator gen) {
     
fill(a, 0, a.length, gen);
  
}
  
  
public static void fill(Object[] a, int from, int to, Generator gen) {
     
for (int i = from; i < to; i++)
        
a[i] = gen.next();
  
}
  
  
public static void fill(boolean[] a, BooleanGenerator gen) {
     
fill(a, 0, a.length, gen);
  
}
  
  
public static void fill(boolean[] a, int from, int to, BooleanGenerator gen) {
     
for (int i = from; i < to; i++)
        
a[i] = gen.next();
  
}
  
  
public static void fill(byte[] a, ByteGenerator gen) {
     
fill(a, 0, a.length, gen);
  
}
  
  
public static void fill(byte[] a, int from, int to, ByteGenerator gen) {
     
for (int i = from; i < to; i++)
        
a[i] = gen.next();
  
}
  
  
public static void fill(char[] a, CharGenerator gen) {
     
fill(a, 0, a.length, gen);
  
}
  
  
public static void fill(char[] a, int from, int to, CharGenerator gen) {
     
for (int i = from; i < to; i++)
        
a[i] = gen.next();
  
}
  
  
public static void fill(short[] a, ShortGenerator gen) {
     
fill(a, 0, a.length, gen);
  
}
  
  
public static void fill(short[] a, int from, int to, ShortGenerator gen) {
     
for (int i = from; i < to; i++)
        
a[i] = gen.next();
  
}
  
  
public static void fill(int[] a, IntGenerator gen) {
     
fill(a, 0, a.length, gen);
  
}
  
  
public static void fill(int[] a, int from, int to, IntGenerator gen) {
     
for (int i = from; i < to; i++)
        
a[i] = gen.next();
  
}
  
  
public static void fill(long[] a, LongGenerator gen) {
     
fill(a, 0, a.length, gen);
  
}
  
  
public static void fill(long[] a, int from, int to, LongGenerator gen) {
     
for (int i = from; i < to; i++)
        
a[i] = gen.next();
  
}
  
  
public static void fill(float[] a, FloatGenerator gen) {
     
fill(a, 0, a.length, gen);
  
}
  
  
public static void fill(float[] a, int from, int to, FloatGenerator gen) {
     
for (int i = from; i < to; i++)
        
a[i] = gen.next();
  
}
  
  
public static void fill(double[] a, DoubleGenerator gen) {
     
fill(a, 0, a.length, gen);
  
}
  
  
public static void fill(double[] a, int from, int to, DoubleGenerator gen) {
     
for (int i = from; i < to; i++)
        
a[i] = gen.next();
  
}
  
  
private static Random r = new Random();
  
  
public static class RandBooleanGenerator implements BooleanGenerator {
     
public boolean next() {
        
return r.nextBoolean();
     
}
   }
  
  
public static class RandByteGenerator implements ByteGenerator {
     
public byte next() {
        
return (byte) r.nextInt();
     
}
   }
  
  
private static String ssource = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
  
private static char[] src = ssource.toCharArray();
  
  
public static class RandCharGenerator implements CharGenerator {
     
public char next() {
        
return src[r.nextInt(src.length)];
     
}
   }
  
  
public static class RandStringGenerator implements Generator {
     
private int len;
     
private RandCharGenerator cg = new RandCharGenerator();
     
     
public RandStringGenerator(int length) {
        
len = length;
     
}
     
     
public Object next() {
        
char[] buf = new char[len];
        
for (int i = 0; i < len; i++)
           
buf[i] = cg.next();
        
return new String(buf);
     
}
   }
  
  
public static class RandShortGenerator implements ShortGenerator {
     
public short next() {
        
return (short) r.nextInt();
     
}
   }
  
  
public static class RandIntGenerator implements IntGenerator {
     
private int mod = 10000;
     
     
public RandIntGenerator() {
      }
     
     
public RandIntGenerator(int modulo) {
        
mod = modulo;
     
}
     
     
public int next() {
        
return r.nextInt(mod);
     
}
   }
  
  
public static class RandLongGenerator implements LongGenerator {
     
public long next() {
        
return r.nextLong();
     
}
   }
  
  
public static class RandFloatGenerator implements FloatGenerator {
     
public float next() {
        
return r.nextFloat();
     
}
   }
  
  
public static class RandDoubleGenerator implements DoubleGenerator {
     
public double next() {
        
return r.nextDouble();
     
}
   }
}
// /:~

Для заполнения массива элементов с использованием генератора метод fill( ) принимает ссылку на соответствующий интерфейс генератора, в котором есть метод next( ), который каким-то образом воспроизводит объект правильного типа (в зависимости от того, какой интерфейс реализован). Метод fill( ) просто вызывает метод next( ) до тех пор, пока необходимый диапазон не будет заполнен. Теперь вы можете создать любой генератор путем реализации соответствующего интерфейса и использовать генератор в методе fill( ).

Генераторы случайных чисел полезны при тестировании, так что набор внутренних классов, созданных для реализации всех интерфейсов примитивных генераторов наряду с генератором для типа String, представляющего Object. Вы можете видеть, что RandStringGenerator использует RandCharGenerator для заполнения массива символов, которые затем превращаются в Striung. Размер массива определяется аргументом конструктора.

Для генерации чисел, которые не являются большими числами, RandIntGenerator берет остаток от деления на 10 000, но перегруженный конструктор позволяет вам выбирать меньшие значения.

[1] Эта издевательская ссылка связана с событиями в Китае после смерти Мао-Дзе Дуна, когда четыре человека, включая вдову Мао, сделали игру с властью и были демонизированы Китайской Коммунистической Партией под этим именем.

[2] От Mark Johnson.

[3] Предупреждаем: пример из C++.

[4] Бесплатная публикация по email. Смотрите www.BruceEckel.com, чтобы подписаться.

[5] Получено по email от Kevlin Henney.

[6] Shalloway, Design Patterns Explained, and Alexandrescu, Advanced C++ Design (??)

[7] В языке Python все функции уже являются объектами, поэтому шаблон Команды часто является избыточным.

[8] Страница 235.

[9] Оригинальная версия была названа JPython, но проект изменился и имя было изменено для явного указания отличия новой версии.

[10] Измените установку в регистре python.security.respectJavaAccessibility = true в false, чтобы сделать тестирование более мощным, поскольку это позволит вам тестировать скрипт с использованием *всех* методов, даже приватных и пакетного уровня доступа.

[11] Ни одна мышь не погибла при создании этого примера.

[12] Addison-Wesley, 1999.

[13] Это решения создано Jaroslav Tulach в дизайне проектирования, который я давал в Праге.

[14] Программисты на C++ заметят, как много кода может быть сжато при использовании аргументов по умолчанию и шаблонов. Программисты на Python заметят, что вся эта библиотека будет громоздкой и ненужной в этом языке.