textgen.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. //====================================================================
  2. // TEXTGEN -- BrainF*** Genetic Text Generator
  3. // textgen.java
  4. // Copyright (C) 2003, 2004 by Jeffry Johnston
  5. // Version 0.20
  6. //
  7. // This program is free software; you can redistribute it and/or
  8. // modify it under the terms of the GNU General Public License as
  9. // published by the Free Software Foundation. See the file LICENSE
  10. // for more details.
  11. //
  12. // Online resource used:
  13. // http://www.genetic-programming.com/gpflowchart.html
  14. //
  15. // I have not read any books and have seen very little information
  16. // online about GP implementation. Any suggestions are most welcome.
  17. //
  18. // Inspiration:
  19. // 111-byte "Hello World!" with newline (assumed optimal)
  20. // ++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.
  21. // <<+++++++++++++++.>.+++.------.--------.>+.>.
  22. //
  23. // Version history:
  24. // 0.20 11 Aug 2004 Added -i and -o options
  25. // 0.10 01 Dec 2003 Initial release
  26. //====================================================================
  27. import java.io.*;
  28. import java.util.*;
  29. //********************************************************************
  30. // textgen
  31. //********************************************************************
  32. public class textgen {
  33. static final String VERSION = "0.20";
  34. static Random r = new Random();
  35. //------------------------------------------------------------------
  36. // main
  37. //------------------------------------------------------------------
  38. public static void main(String[] cmdline) {
  39. int terms = 4, population = 500, generations = 0;
  40. String text = "Hello World!\n";
  41. double toppercent = 0.10;
  42. String fileout = null;
  43. // display copyright text
  44. System.out.println("TEXTGEN BrainF*** Genetic Text Generator, version " + VERSION + ".");
  45. System.out.println("Copyright (c) 2003 Jeffry Johnston.");
  46. System.out.println();
  47. // process command line
  48. for (int n = 0; ; n++) {
  49. try {
  50. if (cmdline[n].equals("-?") || cmdline[n].equals("/?") || cmdline[n].equals("--?")
  51. || cmdline[n].equals("--help") ) {
  52. usage();
  53. System.exit(0);
  54. } else if (cmdline[n].equalsIgnoreCase("-i")) {
  55. // open file
  56. DataInputStream filein = null;
  57. try {
  58. filein = new DataInputStream(new FileInputStream(cmdline[n + 1]));
  59. } catch (FileNotFoundException e) {
  60. System.out.println("Error opening input file. File not found");
  61. System.exit(1);
  62. }
  63. // read file
  64. byte[] data = null;
  65. int size = 0;
  66. try {
  67. size = filein.available();
  68. data = new byte[size];
  69. filein.readFully(data);
  70. } catch (IOException e) {
  71. System.out.println("Error reading input file.");
  72. System.exit(1);
  73. }
  74. // close file
  75. try {
  76. filein.close();
  77. } catch (IOException e) {
  78. System.out.println("Error closing input file.");
  79. System.exit(1);
  80. }
  81. try {
  82. text = new String(data, "ISO-8859-1");
  83. } catch (UnsupportedEncodingException e) {
  84. System.out.println("This program requires ISO-8859-1 character set support.");
  85. }
  86. n++;
  87. } else if (cmdline[n].equalsIgnoreCase("-o")) {
  88. fileout = cmdline[n + 1];
  89. n++;
  90. } else if (cmdline[n].equalsIgnoreCase("-g")) {
  91. generations = Integer.parseInt(cmdline[n + 1]);
  92. if (generations < 1) {
  93. System.out.println("Must have at least 1 generation.");
  94. System.exit(1);
  95. }
  96. n++;
  97. } else if (cmdline[n].equalsIgnoreCase("-t")) {
  98. terms = Integer.parseInt(cmdline[n + 1]);
  99. if (terms < 1) {
  100. System.out.println("Must have at least 1 term.");
  101. System.exit(1);
  102. }
  103. n++;
  104. } else if (cmdline[n].equalsIgnoreCase("-p")) {
  105. population = Integer.parseInt(cmdline[n + 1]);
  106. if (terms < 2) {
  107. System.out.println("Must have population of at least 10.");
  108. System.exit(1);
  109. }
  110. n++;
  111. } else if (cmdline[n].equalsIgnoreCase("-%")) {
  112. int tp = Integer.parseInt(cmdline[n + 1]);
  113. if (tp < 0 || tp > 100) {
  114. System.out.println("Percentage out of range (0, 100).");
  115. System.exit(1);
  116. }
  117. toppercent = (double) tp / 100;
  118. n++;
  119. } else {
  120. text = cmdline[n];
  121. if (text.charAt(0) == '\"') {
  122. text = text.substring(1);
  123. }
  124. if (text.charAt(text.length() - 1) == '\"') {
  125. text = text.substring(0, text.length());
  126. }
  127. }
  128. } catch (ArrayIndexOutOfBoundsException e) {
  129. break;
  130. } catch (NumberFormatException e) {
  131. System.out.println("Invalid number");
  132. System.exit(1);
  133. }
  134. }
  135. int top = (int) (toppercent * population);
  136. int bottom = population - top;
  137. if (fileout == null) {
  138. System.out.println("Text: \"" + text + "\"");
  139. }
  140. System.out.print("Generations: ");
  141. if (generations == 0) {
  142. System.out.println("Infinite");
  143. } else {
  144. System.out.println(generations);
  145. }
  146. System.out.println("Terms: " + terms);
  147. System.out.println("Population: " + population);
  148. System.out.println("Top %: " + toppercent);
  149. System.out.println();
  150. CompareIndividuals compare = new CompareIndividuals();
  151. // generate initial population
  152. Individual[] citizen = new Individual[population];
  153. for (int n = 0; n < population; n++) {
  154. citizen[n] = new Individual(text, terms);
  155. }
  156. // keep looking for the best program
  157. int best = Integer.MAX_VALUE;
  158. for(int generation = 1; (generation <= generations) || (generations == 0);
  159. generation++) {
  160. // choose a top citizen to copy and tweak number order
  161. for (int n = bottom; n < population; n++) {
  162. citizen[n].tweak(citizen[r.nextInt(top)]);
  163. }
  164. // genetic stimulation
  165. for (int n = 1; n < population; n++) {
  166. int p = r.nextInt(population);
  167. if (p < n) { // less mutation with good, more mutation with bad
  168. citizen[n].mutate();
  169. } else {
  170. p = r.nextInt(top);
  171. citizen[n].crossover(citizen[p]);
  172. }
  173. }
  174. // get rid of the duds
  175. Arrays.sort(citizen, compare);
  176. for (int n = bottom; n < population; n++) {
  177. citizen[n].restart();
  178. }
  179. // display progress
  180. Arrays.sort(citizen, compare);
  181. if (citizen[0].getFitness() < best) {
  182. String temptext;
  183. System.out.println((best = citizen[0].getFitness()) + " " + (temptext = citizen[0].getCode())
  184. + " [" + generation + "]");
  185. System.out.println();
  186. if (fileout != null) {
  187. PrintStream out;
  188. try {
  189. out = new PrintStream(new FileOutputStream(fileout));
  190. out.print(temptext);
  191. out.close();
  192. } catch (IOException e) {
  193. System.out.println("Error writing output file.");
  194. System.exit(1);
  195. }
  196. }
  197. }
  198. }
  199. }
  200. //------------------------------------------------------------------
  201. // getRandomBoolean()
  202. //------------------------------------------------------------------
  203. public static boolean getRandomBoolean() {
  204. return r.nextBoolean();
  205. }
  206. //------------------------------------------------------------------
  207. // getRandomInt()
  208. //------------------------------------------------------------------
  209. public static int getRandomInt(int n) {
  210. return r.nextInt(n);
  211. }
  212. //------------------------------------------------------------------
  213. // usage -- prints usage information
  214. //------------------------------------------------------------------
  215. public static void usage() {
  216. System.out.println("Usage: textgen [-i file] [-o file] [-t #] [-p #] [-% #] \"text\" [-?]");
  217. System.out.println("Where: \"text\" Text to generate");
  218. System.out.println(" -i Input filename (in place of \"text\")");
  219. System.out.println(" -o Output filename, default: screen only");
  220. System.out.println(" -g Generations, default: infinite");
  221. System.out.println(" -t Number of terms, default: 4");
  222. System.out.println(" -p Population, default: 500");
  223. System.out.println(" -% Percent to rank as top, default: 10");
  224. System.out.println(" -? Display usage information");
  225. }
  226. }
  227. //********************************************************************
  228. // CompareIndividuals
  229. //********************************************************************
  230. class CompareIndividuals implements Comparator {
  231. public int compare(Object o1, Object o2) {
  232. return (((Individual) o1).getFitness() - ((Individual) o2).getFitness());
  233. }
  234. }
  235. //********************************************************************
  236. // Individual
  237. //********************************************************************
  238. class Individual {
  239. int letters, mult, terms, fitness = -1;
  240. int[] term, finder, fixer;
  241. String expected;
  242. //------------------------------------------------------------------
  243. // (constructor)
  244. //------------------------------------------------------------------
  245. Individual(String expected, int terms) {
  246. this.expected = expected;
  247. letters = expected.length();
  248. this.terms = terms;
  249. term = new int[terms];
  250. restart();
  251. }
  252. //------------------------------------------------------------------
  253. // restart()
  254. //------------------------------------------------------------------
  255. public void restart() {
  256. // multiplier (1 to 15)
  257. mult = textgen.getRandomInt(15) + 1;
  258. // value of each term (0 to 15)
  259. for (int n = 0; n < terms; n++) {
  260. term[n] = textgen.getRandomInt(16);
  261. }
  262. // ><+- multiple before each dot (one for each letter)
  263. finder = new int[letters];
  264. fixer = new int[letters];
  265. for (int n = 0; n < letters; n++) {
  266. finder[n] = textgen.getRandomInt(terms);
  267. }
  268. fitness = -1;
  269. }
  270. //------------------------------------------------------------------
  271. // mutate()
  272. //------------------------------------------------------------------
  273. public void mutate() {
  274. int n;
  275. // mutate item
  276. if (textgen.getRandomBoolean()) {
  277. // term
  278. n = textgen.getRandomInt(terms);
  279. term[n] = textgen.getRandomInt(16);
  280. } else {
  281. // finder
  282. n = textgen.getRandomInt(letters);
  283. finder[n] = textgen.getRandomInt(terms);
  284. }
  285. fitness = -1;
  286. }
  287. //------------------------------------------------------------------
  288. // tweak()
  289. //------------------------------------------------------------------
  290. public void tweak(Individual giver) {
  291. if ((textgen.r).nextBoolean()) {
  292. // copy the giver exactly
  293. mult = giver.getMult();
  294. for (int n = 0; n < terms; n++) {
  295. term[n] = giver.getTerm(n);
  296. }
  297. } else {
  298. // copy the giver, but use a new multiplier, adjust the terms accordingly
  299. mult = textgen.getRandomInt(15) + 1;
  300. for (int n = 0; n < terms; n++) {
  301. term[n] = Math.round(giver.getTerm(n) * giver.getMult() / (float) mult);
  302. }
  303. }
  304. // copy the finder
  305. for (int n = 0; n < letters; n++) {
  306. finder[n] = giver.getFinder(n);
  307. }
  308. // swap two of the terms to see if it helps (can be the same term)
  309. int n1 = textgen.getRandomInt(terms);
  310. int n2 = textgen.getRandomInt(terms);
  311. int n3 = term[n2];
  312. term[n2] = term[n1];
  313. term[n1] = n3;
  314. // swap finders to match swapped terms
  315. for (int n = 0; n < letters; n++) {
  316. if (finder[n] == n1) {
  317. finder[n] = n2;
  318. } else if (finder[n] == n2) {
  319. finder[n] = n1;
  320. }
  321. }
  322. fitness = -1;
  323. }
  324. //------------------------------------------------------------------
  325. // crossover()
  326. //------------------------------------------------------------------
  327. public void crossover(Individual giver) {
  328. int n;
  329. if ((textgen.r).nextBoolean()) {
  330. // term
  331. n = textgen.getRandomInt(terms);
  332. int n2 = textgen.getRandomInt(terms);
  333. term[n2] = giver.getTerm(n);
  334. } else {
  335. // finder
  336. n = textgen.getRandomInt(letters);
  337. finder[n] = giver.getFinder(n);
  338. }
  339. fitness = -1;
  340. }
  341. //------------------------------------------------------------------
  342. // getMult()
  343. //------------------------------------------------------------------
  344. public int getMult() {
  345. return mult;
  346. }
  347. //------------------------------------------------------------------
  348. // getTerms()
  349. //------------------------------------------------------------------
  350. public int getTerms() {
  351. return terms;
  352. }
  353. //------------------------------------------------------------------
  354. // getTerm()
  355. //------------------------------------------------------------------
  356. public int getTerm(int n) {
  357. return term[n];
  358. }
  359. //------------------------------------------------------------------
  360. // getFinder()
  361. //------------------------------------------------------------------
  362. public int getFinder(int n) {
  363. return finder[n];
  364. }
  365. //------------------------------------------------------------------
  366. // getFitness()
  367. //------------------------------------------------------------------
  368. public int getFitness() {
  369. if (fitness < 0) {
  370. int length = 4 + mult + terms * 2 + letters; // mult [ terms> terms< -]> letters.
  371. int[] memory = new int[terms];
  372. for (int n = 0; n < terms; n++) {
  373. length += term[n];
  374. memory[n] = (mult * term[n]) % 256;
  375. }
  376. int mp = 0;
  377. for (int n = 0; n < letters; n++) {
  378. length += Math.abs(finder[n] - mp);
  379. mp = finder[n];
  380. fixer[n] = expected.charAt(n) - memory[mp];
  381. int n2 = Math.abs(fixer[n]);
  382. if (Math.abs(fixer[n]) > 128) {
  383. n2 = 256 - n2;
  384. if (fixer[n] > 0) {
  385. n2 = -n2;
  386. }
  387. }
  388. length += Math.abs(fixer[n]);
  389. memory[mp] = expected.charAt(n);
  390. }
  391. // combine results
  392. fitness = length;
  393. if (fitness < 0) { fitness = Integer.MAX_VALUE; }
  394. }
  395. return fitness;
  396. }
  397. //------------------------------------------------------------------
  398. // getCode()
  399. //------------------------------------------------------------------
  400. public String getCode() {
  401. String code = getBF(mult, '+', '+') + "[";
  402. for (int n = 0; n < terms; n++) {
  403. code += ">" + getBF(term[n], '+', '+');
  404. }
  405. code += getBF(terms, '<', '<') + "-]>";
  406. int mp = 0;
  407. for (int n = 0; n < letters; n++) {
  408. code += getBF(finder[n] - mp, '>', '<');
  409. mp = finder[n];
  410. code += getBF(fixer[n], '+', '-') + ".";
  411. }
  412. return code;
  413. }
  414. //------------------------------------------------------------------
  415. // getBF() {
  416. //------------------------------------------------------------------
  417. public String getBF(int n, char pos, char neg) {
  418. char ch = ' ';
  419. if (n > 0) {
  420. ch = pos;
  421. } else if (n < 0) {
  422. ch = neg;
  423. }
  424. n = Math.abs(n);
  425. if (n != 0) {
  426. char[] c = new char[Math.abs(n)];
  427. Arrays.fill(c, ch);
  428. return new String(c);
  429. } else {
  430. return "";
  431. }
  432. }
  433. }