Sample Program Code


This page contains various small programs I've written in answer to some peoples' questions. They're samples to demonstrate a technique or answer a question. This page is long, so here's a short index:
BlackWhite.c - a C++ program (my first!) to solve a puzzle. The game principle is explained in the comments.
HighLow.c - A C solution for the high-low number guessing game.
HighLow.cpp - The same thing, translated to (very C-like) C++.
TimeCalc.java - A Java applet / GUI application to convert Astonian time to real time and vice versa.
/*
BlackWhite.c -- a program to solve a puzzle.
Carl [Elrac] Smotricz, September 2001
Idea by Corwin
*/
#include <iostream>
#include <string>
#include <queue>
#include <stack>
/*
The board has 9 squares, each of which can be white, gray or black. I call the different possible configurations of colors on the squares a "pattern". Each square has 1 of 3 possible color values, and there are 9 squares, so each possible pattern can be represented by 9-digit number in base 3 (written with only the digits 0...2) or its decimal equivalent. There are 3^9 or 19683 different possible patterns. Thus, each pattern fits comfortably into a 16 bit unsigned integer.
*/
typedef unsigned int pattern;
const int COLORS = 3;
const int ROWS = 3;
const int COLS = 3;
const int SQUARES = ROWS * COLS;
const int PATTERNS = 3*3*3 * 3*3*3 * 3*3*3;
const int GOAL = PATTERNS - 1;
const int NONE = PATTERNS;
const char SYMBOLS[] = { ':', '+', '#' };
/*
An array to hold the 9 numbers representing a given pattern. Defined module-global and static for efficiency. At any given time, we only need one such broken-out pattern.
*/
int values[SQUARES];
/*
A table of patterns to keep track of where we've been. This is surprisingly simple: The position in the table represents the pattern, and the only information we really need is whether the pattern has been examined before, and from which other pattern that examination originated. The originating pattern is a number from 0...PATTERNS-1. We'll mark an unvisited pattern with the number NONE, which is = PATTERNS and thus the smallest number too large to be a valid pattern.
*/
pattern from[PATTERNS];
/*
A list showing which move was made to reach a pattern.
*/
int move[PATTERNS];
/*
The breadth-first search needs a queue to keep track of where it's searching. This is it.
*/
queue<pattern> que;
/*
This stack required for backtracking from the goal to the start.
*/
stack<pattern> stk;
/*
A function to pack the numbers in "values" into a "pattern".
*/
pattern pack() {
  int result = 0;
  for (int j=0; j<SQUARES; j++) result = COLORS * result +
    values[j];
  return result;
}
/*
A function to unpack a pattern into "values".
*/
void unpack(pattern pat) {
  for (int j=SQUARES; j>0; ) {
    values[--j] = pat % COLORS;
    pat /= COLORS;
  }
}
/*
A function to "roll" a number in "values". This means: Add 1, but cycle to 0 after 2. The short name is needed to make the "touch" function compact (see below).
*/
void r(int which) { 
  values[which] = (values[which] == COLORS - 1) ? 0 : values[which] + 1;
}
/*
A function to "touch" a number in "values". This means: "roll" the given number and all those adjacent. Squares are numbered like this:
0 1 2
3 4 5
6 7 8
This function is "hardwired" for a 3 x 3 square for efficiency.
*/
void touch(int which) {
  switch (which) {
    case 0: r(0); r(1); r(3); r(4); break; 
    case 1: r(0); r(1); r(2); r(3); r(4); r(5); break; 
    case 2: r(1); r(2); r(4); r(5); break; 
    case 3: r(0); r(1); r(3); r(4); r(6); r(7); break; 
    case 4: r(0); r(1); r(2); r(3); r(4); r(5); r(6); r(7); r(8); break; 
    case 5: r(1); r(2); r(4); r(5); r(7); r(8); break; 
    case 6: r(3); r(4); r(6); r(7); break; 
    case 7: r(3); r(4); r(5); r(6); r(7); r(8); break; 
    case 8: r(4); r(5); r(7); r(8); break;
  }
}
/*
Print out a pattern, with a caption.
*/
void print(pattern pat, string caption) {
  string filler = string(caption.size(), ' ');
  int p = 0;
  unpack(pat); // just in case it isn't
  for (int row=0; row<ROWS; row++) {
    if (row == 0) {
      cout << caption;
    } else {
      cout << filler;
    }
    for (int col=0; col<COLS; col++) {
      cout << string(" ") + SYMBOLS[values[p++]];
    }
    cout << endl;
  }
  cout << endl;
}
/*
Tell the user how to work this program.
*/
void useage(const char *progname) {
  cerr << "Useage: " << progname << " initialpattern" << endl;
  cerr << " initialpattern = string of 9 digits from 1 - 3" << endl;
}

int main(int argc, const char *argv[]) {

  // Make the output a bit readable
  cout << endl;

  // Check for argument
  if (argc < 2) {
    useage(argv[0]);
    return 1;
  }

  // Check for a 9 character argument
  if (strlen(argv[1]) < SQUARES) {
    useage(argv[0]);
    return 1;
  }

  // Try to unpack a string of digits
  for (int j=0; j<SQUARES; j++) {
    char digit = argv[1][j];
    if (!isdigit(digit)
    || digit == '0'
    || digit - '1' >= COLORS) {
      useage(argv[0]);
      return 1;
    }
    values[j] = digit - '1';
  }

  // Initialize pattern table
  for (int j=0; j<PATTERNS; j++) from[j] = NONE;

  // Calculate the initial pattern
  pattern start = pack();
  print(start, "Initial:");

  // Mark the initial pattern as visited by itself
  from[start] = start;

  // put the starting pattern on the queue
  que.push(start);

  // explore until the queue is empty or pattern GOAL is found.
  while (!que.empty()) {
    pattern origin = que.front();
    que.pop();
    for (int j=0; j<SQUARES; j++) {
      unpack(origin);
      touch(j);
      pattern destination = pack();
      if (from[destination] != NONE) continue;
        from[destination] = origin;
        move[destination] = j;
        if (destination == GOAL) break;
        que.push(destination);
    }
  }

  // Check if successful
  if (from[GOAL] == NONE) {
    cout << "No solution found." << endl;
    return 0;
  }

  // Walk back the trail
  for (pattern pat=GOAL; pat!=start; pat=from[pat]) stk.push(pat);
  int moves = stk.size();

  // Display the moves
  string caption = "touch x:";
  while (!stk.empty()) {
    pattern pat = stk.top();
    caption[6] = '1' + move[pat];
    print(stk.top(), caption);
    stk.pop();
  }

  // Some statistics
  int count = 0;
  for (int j=0; j<PATTERNS; j++) {
    if (from[j] != NONE) count++;
  }
  cout << "Moves: " << moves << "; patterns examined: " << count << "." << endl;
}
On May 01, 2001, Elrac said:

The High-Low game, in C
This solution is written in ANSI standard C, which should compile with most any C compiler west of Timbuktu, even Corwin's C++ compiler. It uses C (not C++) standard I/O, which you can replace with cin and cout if you like. It also demonstrates constants, a while loop, a switch statement terminated by a return, and the use of continue. No ifs required for this version.
#include <stdio.h>
const int MIN = 1, MAX = 100;
int main(int argc, char *argv[]) {
  char answer;
  int low = MIN, high = MAX;
  int guess;
  while (low <= high) {
    guess = (low + high) / 2;
    printf("I choose: %d\n"
           "Is that correct (c), too low (l) or too high (h)?", guess);
    scanf(" %c", &answer);
    switch (answer) {
      case 'c':
      case 'C':
        printf("Thank you, I knew I could do it!\n");
        return 0;
      case 'l':
      case 'L':
        low = guess + 1;
        continue;
      case 'h':
      case 'H':
        high = guess - 1;
        continue;
    }
    printf("Please answer with 'c', 'l' or 'h'!\n");
  }
  printf("You seem to have contradicted yourself;\n"
         "I don't want to play any more.\n");
  return 1;
}
On May 02, 2001, Elrac said:

The High-Low game in C++
Jagu, I don't speak Spanish but I understood every word of that sentence you wrote. By the same token, it should be no problem for you to bridge the gap from iostream to stdio, printf to cout and scanf to cin. Still, you asked for C++, so here it is:
#include <iostream.h>
const int MIN = 1, MAX = 100;
int main(int argc, char *argv[]) {
  char answer;
  int low = MIN, high = MAX;
  int guess;
  while (low <= high) {
    guess = (low + high) / 2;
    cout << "I choose: " << guess << endl <<
            "Is that correct (c), too low (l) or too high (h)?";
    cin >> answer;
    switch (answer) {
      case 'c':
      case 'C':
        cout << "Thank you, I knew I could do it!" << endl;
        return 0;
      case 'l':
      case 'L':
        low = guess + 1;
        continue;
      case 'h':
      case 'H':
        high = guess - 1;
        continue
    }
    cout << "Please answer with 'c', 'l' or 'h'!" << endl;
  }
  cout << "You seem to have contradicted yourself;" << endl <<
          "I don't want to play any more." << endl;
  return 1;
}
Anything you don't understand here shows a lack understanding of the C (ok, C++) language which you should work on correcting. Feel welcome to ask here about anything!

Some notes to Ironbreeze:
  1. Thanks for the kind words. I do what I can!
  2. This message board destroys indentation unless you use the [ code ] tag, as I did. That's what it's for.
  3. Bit shifting to divide/multiply by powers of 2 is "cool" and correct, but as you say it can be confusing to others and is (usually) very unnecessary. Any decent compiler these days will perform this optimization automatically.
TimeCalcTest
This is a Java implementation of a converter for Astonian time, which runs at 18x "real" time.
<html>
  <head>
    <title>Astonia Time Converter Test Page</title>
  <body>
    <h1 align="center">Astonia Time Converter Test Page</h1>
    <table align="center">
      <tr>
        <td>
          <applet code="TimeCalc.class" codebase="/opensource/timecalc" 
                       height=100 width=400 name="TimeCalc">
            You should see a Java applet here. The fact that you don't means
            that your browser isn't able to display Java applets, perhaps 
            because you disabled Java in your settings.
          </applet>
        </td>
      </tr>
    </table>
  </body>
</html>


TimeCalc.java
import java.applet.*;
import java.awt.*;
import java.awt.event.*;

/**
 * This is a dual-mode Java applet; it can be run as an applet
 * in a browser from a suitably prepared Web page or it can be
 * run as an application using "java TimeCalc".
 * It's pretty obvious that most of the actual work is done by
 * class TimeCalcGui, below.
 */

public class TimeCalc extends Applet {
  /**
   * Create the applet
   */
  public TimeCalc() {
    setLayout(new BorderLayout());
    add(new TimeCalcGui(), BorderLayout.CENTER);
  }
  /**
   * Run the whole thing as an application.
   * (java TimeCalc)
   */
  public static void main(String[] args) {
    Frame mainFrame = new Frame();
    mainFrame.setTitle("Astonia Time Converter");
    // Enable ALT-F4 and 'X' to close program
    mainFrame.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent we) {
        System.exit(0);
      }
    });
    mainFrame.add(new TimeCalcGui(), BorderLayout.CENTER);
    mainFrame.pack();
    mainFrame.show();
  }
} //end class TimeCalc

/**
 * This is the actual works of TimeCalc.
 * It's all built on a Panel, which is then either inserted 
 * into a Frame or an Applet.
 */
class TimeCalcGui extends Panel {
  /** Checkboxes */
  CheckboxGroup group = new CheckboxGroup();
  Checkbox checkA2R = new Checkbox("Astonia to Real time", true, group),
           checkR2A = new Checkbox("Real to Astonia time", false, group);
             
  /** Input fields */
  TextField inMonths = new TextField(2),
            inDays = new TextField(2),
            inHours = new TextField(2),
            inMinutes = new TextField(2),
            inSeconds = new TextField(2);

  /** Output */
  TextArea resultArea = new TextArea("", 2, 40, TextArea.SCROLLBARS_NONE);
    
  /** Mode: Astonia to Real if true, Real to Astonia if false. */
  boolean modeA2R = true;
    
  /**
   * Construct the GUI, setting everything up
   */
  public TimeCalcGui() {
    setLayout(new BorderLayout());
    // At the top is a panel with input fields for time components
    Panel inputPanel = new Panel(new GridLayout(2, 5));
    inputPanel.setBackground(Color.lightGray);
    inputPanel.add(new Label("Months"));
    inputPanel.add(new Label("Days"));
    inputPanel.add(new Label("Hours"));
    inputPanel.add(new Label("Minutes"));
    inputPanel.add(new Label("Seconds"));
    inputPanel.add(inMonths);
    inputPanel.add(inDays);
    inputPanel.add(inHours);
    inputPanel.add(inMinutes);
    inputPanel.add(inSeconds);
    add(inputPanel, BorderLayout.NORTH);

    // In the middle is a TextArea into which the results are written.
    resultArea.setEnabled(false);
    add(resultArea, BorderLayout.CENTER);

    // At the bottom is a CheckBoxGroup asking which way to convert,
    // and a button to start the process
    Panel whichWayPanel = new Panel(new FlowLayout());
    ItemListener checkListener = new ItemListener() {
      public void itemStateChanged(ItemEvent ie) {
        TimeCalcGui.this.modeSelected(ie);
      }
    };
    whichWayPanel.setBackground(Color.lightGray);
    checkA2R.addItemListener(checkListener);
    whichWayPanel.add(checkA2R);
    checkR2A.addItemListener(checkListener);
    whichWayPanel.add(checkR2A);
    ActionListener buttonListener = new ActionListener() {
      public void actionPerformed(ActionEvent ae) {
        TimeCalcGui.this.convert();
      }
    };
    Button convertButton = new Button("Convert!");
    convertButton.addActionListener(buttonListener);
    whichWayPanel.add(convertButton);
    add(whichWayPanel, BorderLayout.SOUTH);
  }
    
  /**
   * "Convert" button pushed: Do the conversion.
   */
  public void convert() {
    try {
      int iSeconds = valueOfField(inSeconds);
      int iMinutes = valueOfField(inMinutes);
      int iHours   = valueOfField(inHours);
      int iDays    = valueOfField(inDays);
      int totalSeconds = iSeconds + 60 * (iMinutes + 60 * (iHours + 24 * iDays));
      if (modeA2R) {
        int iMonths = valueOfField(inMonths);
        totalSeconds += 28 * 24 * 60 * 60 * iMonths;
      }
      if (totalSeconds == 0) {
        resultArea.setText("0 hours.");
      } else {
        int oSeconds = modeA2R ? (totalSeconds / 18) : (totalSeconds * 18);
        int oMinutes = oSeconds / 60;
        oSeconds -= 60 * oMinutes;
        int oHours = oMinutes / 60;
        oMinutes -= 60 * oHours;
        int oDays = oHours / 24;
        oHours -= 24 * oDays;
        int oMonths = 0;
        if (!modeA2R) {
          oMonths = oDays / 28;
          oDays -= 28 * oMonths;
        }
        StringBuffer sb = new StringBuffer();
        if (oMonths != 0) {
          sb.append(String.valueOf(oMonths) + " month");
          if (oMonths > 1) sb.append("s");
        }
        if (oDays != 0) {
          if (sb.length() > 0) sb.append(", ");
          sb.append(String.valueOf(oDays) + " day");
          if (oDays > 1) sb.append("s");
        }
        if (oHours != 0) {
          if (sb.length() > 0) sb.append(", ");
          sb.append(String.valueOf(oHours) + " hour");
          if (oHours > 1) sb.append("s");
        }
        if (oMinutes != 0) {
          if (sb.length() > 0) sb.append(", ");
          sb.append(String.valueOf(oMinutes) + " minute");
          if (oMinutes > 1) sb.append("s");
        }
        if (oSeconds != 0) {
          if (sb.length() > 0) sb.append(", ");
          sb.append(String.valueOf(oSeconds) + " second");
          if (oSeconds > 1) sb.append("s");
        }
        sb.append(".");
        resultArea.setText(sb.toString());
      }
    }
    catch (NumberFormatException nfe) {
    }
  }
    
  /**
   * A2R or R2A mode selected.
   */
  public void modeSelected(ItemEvent ie) {
    Object item = ie.getItemSelectable();
    if (ie.getStateChange() != ItemEvent.SELECTED) return;
    inMonths.setText("");
    inDays.setText("");
    inHours.setText("");
    inMinutes.setText("");
    inSeconds.setText("");
    resultArea.setText("");
    if (item == checkA2R) {
      inMonths.setVisible(true);
      inMonths.requestFocus();
      modeA2R = true;
    }
    if (item == checkR2A) {
      inMonths.setText("");
      inMonths.setVisible(false);
      inDays.requestFocus();
      modeA2R = false;
    }
  }
    
  /**
   * Try to get a value out of a field
   */
  private int valueOfField(TextField tf) 
  throws NumberFormatException {
    String s = tf.getText().trim();
    if (s.equals("")) return 0;
    try {
      int n = Integer.parseInt(s); 
      if (n < 0) throw new NumberFormatException();
      return n;
    }
    catch (NumberFormatException nfe) {
      resultArea.setText("Error in input!");
      tf.requestFocus();
      throw nfe;
    }
  }
} //end class TimeCalcGui
Back to Game Programming

Back to Table Of Contents