tag:blogger.com,1999:blog-75251717671946941252024-02-19T12:44:28.022+01:00Orden y conciertoInformática geek, matemáticas, pensamiento crítico y alguna otra cosa de vez en cuando.pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.comBlogger50125tag:blogger.com,1999:blog-7525171767194694125.post-4951076791364352782017-04-13T13:00:00.000+02:002017-04-13T13:00:04.357+02:00Generador de palabras<p>Hace mucho tiempo, en una gal... Perdón, que me despisto. Hace mucho tiempo, decía, tuve un QL. Era un ordenador bastante interesante. Una de las aplicaciones que incluía era el <em>Archive</em>, un gestor de base de datos con un lenguaje de programación incorporado. Siendo el primer lenguaje estructurado que aprendía, me fascinó el hecho de que no tenía GOTO, y sin embargo se seguía pudiendo hacer lo mismo que en BASIC, pero de forma más ordenada y disciplinada. Además, tenía una característica que no he vuelto a ver en otro lenguaje de programación hasta que apareció el <em>Python 3</em>, y es que permitía usar ñ y letras acentuadas en los identificadores. Pero me estoy desviando de nuevo...
</p>
<p>Uno de los programas que hice en <em>Archive</em> era un generador de palabras. La idea era mostrar una lista de palabras generadas, que sonaran más o menos como cualquier otra palabra castellana. Las primeras versiones no eran muy útiles, pero fui refinándolo hasta que estuve satisfecho. Fue divertido; algunos de mis amigos lo encontraron fascinante.
</p>
<p>Para hacer un generador de palabras decente, no basta con juntar letras al azar; eso es un poco como usar <a href="https://es.wikipedia.org/wiki/Bogosort"><em>bogosort</em></a> para ordenar: la cantidad de palabras siquiera con un mínimo interés es demasiado reducida entre toda la morralla, con lo que uno se cansa antes de encontrar una. Para abordar el problema adecuadamente, hay que tener en cuenta la estructura de la sílaba en castellano.
</p>
<p>Las primeras versiones de aquel programa generaban un número al azar de sílabas, escogiendo la cabeza (o <em>ataque</em>, según versiones), cima (o <em>núcleo</em>) y coda también al azar. El resultado fue peor de lo que esperaba. Tenía que tener en cuenta también la frecuencia de las letras y de los componentes de la sílaba, incluyendo el vacío. Con ese y otros refinamientos (como reglas de exclusión, donde «n» no puede ir seguida de «p» y otras), al final conseguí uno que daba un resultado digno de enseñar a mis amigos. Seguía generando bastantes palabras inservibles, como «brunspridreñustro», pero al menos había entre ellas unas cuantas que sí eran buenas.
</p>
<p>Lamentablemente, estoy hablando de memoria, porque perdí ese programa cuando devolví el QL. Como ordenador me gustaba, pero el medio de almacenamiento (microdrives) era horrendo, y cada dos semanas me tocaba hacer un viaje al servicio técnico para una alineación de cabezales. Pudo con mi paciencia.
</p>
<p>Pero desde entonces siempre he tenido el gusanillo de tener un generador de palabras. Recientemente me puse manos a la obra y me saqué esa espina. Con más bagaje en mi haber, y la experiencia anterior, me decidí a enfocarlo desde un punto de vista que debería generar palabras de aún mejor calidad que aquel programa de <em>Archive</em>.
</p>
<p>Para hacer que sonara castellano, la cabeza, cima y coda debían no sólo obedecer las reglas de formación silábica, sino además tener aproximadamente la misma frecuencia que en nuestro idioma. Consideré usar cadenas de Markov, pero rechacé la idea por temor a que no generaran suficiente variedad, que el resultado careciera de «imaginación». En vez de eso, decidí que el programa debía generar sílabas con frecuencias separadas para las sílabas primera, última e intermedias, además de para monosílabos, bajo la hipótesis de que las frecuencias varían mucho en esos casos especiales. Pero ¿cómo obtener esas frecuencias?
</p>
<p>La respuesta era obvia: usaría un texto castellano, separándolo en sílabas y sus componentes y computando las frecuencias deseadas. Así que el primer paso sería hacer un separador silábico de textos.
</p>
<p>Lo escribí en Python 3. Las expresiones regulares son un invento maravilloso que usado adecuadamente puede ahorrar mucho tiempo, y en este caso la tarea <em>íntegra</em> de la división silábica recae sobre una sola expresión regular. He aquí el programa:
</p>
<pre class="code"><code>
#!/usr/bin/env python3
# encoding: UTF-8
import sys
import re
# Este módulo carece de una tabla de excepciones a las reglas de separación
# silábica. Por ello, algunas divisiones no las hace correctamente.
# Por ejemplo:
# liando -> li-an-do. Considera 'ia' como diptongo y la divide como lian-do.
# subrayar -> sub-ra-yar. La divide como su-bra-yar.
expresión_sin_vocales = re.compile('^[^aeiouáéíóúü]*$')
expresión_sílaba = re.compile(
'('
# ataques normales
'[bcfgkp][lr]|tr|dr|rr|ll|ch|qu|[bcdfghj-nprstv-zñ]'
# todas las consonantes a comienzo de palabra
'|^[^aeiouáéíóúü]+(?=[aeiouáéíóúü])'
'|)' # no usamos '?' porque nos interesa cadena vacía en vez de Null
# núcleo
# palabras como ahu-yen-tar y a-hue-car son un reto para la expresión regular
'([iuü]h?[aeoáéó]h?[iu]|[iuü]h?[aeoáéó]|[aeoáéó]h?[iu](?!h?[aeoáéó])|[iu]h?[iuíú]|[aeiouáéíóúü])'
# coda = todas las consonantes que no empiezan una nueva sílaba
# (así que repetimos la subexpresión del ataque aquí)
'('
'(?:(?!(?:[bcfgkp][lr]|tr|dr|rr|ll|ch|qu|[bcdfghj-nprstv-zñ])[aeiouáéíóúü])'
'[^aeiouáéíóúü])*'
')'
)
def silabea(palabra, separar_partes_sílaba = True):
# si no tiene vocales, devuelve la palabra intacta
# p.ej. "y", "sr", etc.)
if expresión_sin_vocales.search(palabra):
if separar_partes_sílaba:
return palabra + '::'
else:
return palabra
estado = 0
cabeza = 0
cima = 0
coda = 0
total = ''
acumulado = '' # nos servirá para saber si nos dejamos algo al final
fin_sílaba_anterior = 0
for sílaba in expresión_sílaba.finditer(palabra):
# Comprobar si nos hemos dejado algo entre la coincidencia anterior y esta.
if sílaba.start(0) != fin_sílaba_anterior:
fragmento = palabra[fin_sílaba_anterior:sílaba.start(0)]
total = total + '-(' + fragmento + ')'
acumulado = acumulado + fragmento
del fragmento
acumulado = acumulado + sílaba.group(0)
fin_sílaba_anterior = sílaba.end(0)
if separar_partes_sílaba:
total = total + '-' + sílaba.group(1) + ':' + sílaba.group(2) + ':' + sílaba.group(3)
else:
total = total + '-' + sílaba.group(0)
assert(len(acumulado) >= len(total))
if len(acumulado) > len(palabra):
total = total + '-(' + palabra[len(acumulado):] + ')'
return total[1:]
if __name__ == "__main__":
divide = len(sys.argv) > 2
for x in sys.stdin.readlines():
if x[-1:] == u'\n':
x = x[:-1]
print(silabea(x, divide))
</code></pre>
<p>La entrada debe ser una lista de palabras, a palabra por línea. La salida es la misma lista, pero con guiones entre las sílabas y con símbolos «:» separando cabeza, cima y coda de cada sílaba. Si se añade un argumento cualquiera, entonces no separa los componentes de cada sílaba, sólo las sílabas en sí, es decir, sólo añade guiones. Toma decisiones arbitrarias en casos dudosos, ya que para este propósito no me importa de qué forma se dividan las palabras que admiten más de una separación (como <em>atlántico</em>).
</p>
<p>El texto que escogí para extraer las frecuencias fue <a href="http://www.gutenberg.org/ebooks/17013"><em>Fortunata y Jacinta</em></a>, disponible libremente a través del proyecto Gutenberg. La mayoría del trabajo auxiliar a la separación lo hice con comandos estándar de Unix, sobre todo <kbd>sed</kbd>, pero también <kbd>sort</kbd>, <kbd>uniq</kbd>, <kbd>tr</kbd>, <kbd>cut</kbd> y otros. La tabla final de frecuencias se puede consultar abriendo el código fuente de esta página. Hay doce tablas en total, correspondientes a ataque (A), núcleo (N) y coda (C) de monosílabos (1), inicios de palabra (I), sílabas medias (M) y finales de palabra (F).
</p>
<p>La frecuencia de las longitudes (en sílabas) ha sido también analizada y es respetada por el programa. Puede generar hasta 8 sílabas. Las palabras (sin repetición) más frecuentes en el texto de Galdós son las trisílabas, seguidas de las tetrasílabas. Me remito de nuevo al código fuente para más detalles.
</p>
<p>Sigue habiendo algo de morralla en el resultado, pero la densidad de palabras interesantes es ahora muy satisfactoria. Incluso genera con cierta frecuencia una palabra que ya existe o se diferencia en muy poco de una existente. No genera tildes, así que la acentuación es cosa de cada cual. En ocasiones, una palabra es «casi perfecta», a falta sólo de un pequeño toque que le podemos dar manualmente.
</p>
<p>Ya sin más preámbulos, he aquí un ejemplo del resultado: una colección de palabras recién generada. ¡Que la disfrutéis!
</p>
<script type="text/javascript">
<!--
// starts, vowels, endings with their probabilities
var gp_SILABA1A, gp_SILABA1N, gp_SILABA1N;
var gp_SILABAIA, gp_SILABAIN, gp_SILABAIN;
var gp_SILABAMA, gp_SILABAMN, gp_SILABAMN;
var gp_SILABAFA, gp_SILABAFN, gp_SILABAFN;
// frequency tables
var gp_LEN = [
284, 4781, 10712, 9649, 3246, 541, 73, 4
];
gp_SILABA1A = {
"":36568,
"b":1114,
"bl":10,
"c":4997,
"ch":14,
"cl":2,
"cr":301,
"d":22690,
"f":1080,
"fl":29,
"fr":4,
"g":14,
"gr":324,
"h":2436,
"j":411,
"l":30766,
"ll":1,
"m":8757,
"n":8737,
"p":4878,
"pl":41,
"pr":11,
"qu":17933,
"r":114,
"s":16495,
"t":4036,
"tr":338,
"v":2301,
"x":3,
"y":16182,
"z":3,
};
gp_SILABA1N =
{
"a":39742,
"ai":8,
"au":103,
"e":74960,
"ei":58,
"i":6923,
"ia":47,
"ie":1705,
"ii":29,
"io":869,
"o":29053,
"oi":3,
"u":11392,
"ua":641,
"ue":1706,
"uha":1,
"ui":73,
};
gp_SILABA1C =
{
"":117455,
"b":5,
"c":9,
"d":29,
"f":7,
"h":284,
"l":14749,
"m":36,
"n":21648,
"p":1,
"r":5304,
"s":17154,
"x":15,
"y":2686,
"z":1208,
};
gp_SILABAIA =
{
"":53742,
"b":4051,
"bl":120,
"br":577,
"c":22132,
"ch":1003,
"cl":456,
"cr":1321,
"d":15570,
"dr":38,
"f":4747,
"fl":116,
"fr":666,
"g":2533,
"gl":60,
"gr":944,
"h":9621,
"j":2048,
"l":3778,
"ll":1649,
"m":14751,
"n":4426,
"p":20557,
"pl":566,
"pr":3522,
"ps":5,
"qu":2557,
"r":6953,
"rr":1,
"s":14061,
"t":10762,
"tr":2004,
"v":7700,
"w":4,
"y":56,
"z":177,
"ñ":1,
};
gp_SILABAIN =
{
"a":61576,
"ahi":5,
"ahu":15,
"ai":285,
"au":1028,
"e":57449,
"ehi":2,
"ehu":7,
"ei":346,
"eu":82,
"i":24602,
"ia":371,
"ie":4791,
"ihu":1,
"ii":19,
"io":103,
"iu":134,
"o":38069,
"ohi":21,
"oi":43,
"ou":5,
"u":16108,
"ua":2434,
"ue":5033,
"ui":738,
"uo":7,
"uu":1,
};
gp_SILABAIC =
{
"":145126,
"b":381,
"bs":41,
"c":554,
"d":253,
"f":1,
"g":194,
"l":5703,
"m":5405,
"mp":1,
"n":22868,
"ns":478,
"p":37,
"r":11919,
"rs":8,
"s":18989,
"t":24,
"w":1,
"x":1009,
"y":2,
"z":281,
};
gp_SILABAMA =
{
"":2805,
"b":6460,
"bl":857,
"br":1166,
"c":15652,
"ch":1064,
"cl":460,
"cr":490,
"d":10741,
"dr":442,
"f":3037,
"fl":131,
"fr":226,
"g":5871,
"gl":201,
"gr":673,
"h":775,
"j":2187,
"l":7122,
"ll":1374,
"m":11195,
"n":9537,
"p":7491,
"pl":848,
"pr":1209,
"qu":2653,
"r":12066,
"rr":2061,
"s":9018,
"t":19077,
"tr":2197,
"v":5203,
"w":1,
"x":707,
"y":514,
"z":1100,
"ñ":1943,
};
gp_SILABAMN =
{
"a":39905,
"ahu":1,
"ai":53,
"au":59,
"e":32543,
"ehu":1,
"ei":45,
"eu":39,
"i":39660,
"ia":1574,
"ie":5520,
"ihu":1,
"ii":1,
"io":1750,
"iu":8,
"o":15622,
"oi":8,
"u":9428,
"ua":449,
"ue":1149,
"ui":687,
"uo":47,
"uu":4,
};
gp_SILABAMC =
{
"":116797,
"c":940,
"d":5,
"g":171,
"l":1013,
"m":740,
"mp":1,
"n":17535,
"ns":50,
"p":192,
"r":7827,
"rs":7,
"s":3054,
"ss":2,
"t":12,
"x":47,
"z":161,
};
gp_SILABAFA =
{
"":14173,
"b":9364,
"bl":1450,
"br":2917,
"c":13919,
"ch":3207,
"cl":33,
"cr":12,
"d":28036,
"dr":852,
"f":472,
"fl":13,
"fr":41,
"g":5901,
"gl":133,
"gr":376,
"h":136,
"j":5145,
"k":7,
"l":8803,
"ll":5249,
"m":9950,
"n":15491,
"p":3415,
"pl":147,
"pr":529,
"qu":3087,
"r":21637,
"rr":994,
"s":12822,
"t":30307,
"tr":3782,
"v":3208,
"w":1,
"x":330,
"y":1260,
"z":3315,
"ñ":2761,
};
gp_SILABAFN =
{
"a":84824,
"ai":97,
"au":6,
"e":40271,
"ei":70,
"i":5351,
"ia":3988,
"ie":783,
"ii":6,
"io":6720,
"iu":10,
"o":68729,
"ou":4,
"u":914,
"ua":422,
"ue":805,
"uei":1,
"ui":173,
"uo":101,
};
gp_SILABAFC =
{
"":150185,
"c":9,
"d":4077,
"f":1,
"h":1,
"j":31,
"k":1,
"l":2599,
"m":6,
"n":11750,
"r":11794,
"s":31888,
"t":13,
"x":3,
"y":242,
"z":675,
};
/* TODO: seed RNG */
function gp_prepare_weights(src)
{
var sum = 0;
var dst = [];
for (k in src)
{
sum = sum + src[k];
dst[dst.length] = [sum, k];
}
return dst;
}
// Transform the tables in-place to accumulated weights
gp_SILABA1A = gp_prepare_weights(gp_SILABA1A);
gp_SILABA1N = gp_prepare_weights(gp_SILABA1N);
gp_SILABA1C = gp_prepare_weights(gp_SILABA1C);
gp_SILABAIA = gp_prepare_weights(gp_SILABAIA);
gp_SILABAIN = gp_prepare_weights(gp_SILABAIN);
gp_SILABAIC = gp_prepare_weights(gp_SILABAIC);
gp_SILABAMA = gp_prepare_weights(gp_SILABAMA);
gp_SILABAMN = gp_prepare_weights(gp_SILABAMN);
gp_SILABAMC = gp_prepare_weights(gp_SILABAMC);
gp_SILABAFA = gp_prepare_weights(gp_SILABAFA);
gp_SILABAFN = gp_prepare_weights(gp_SILABAFN);
gp_SILABAFC = gp_prepare_weights(gp_SILABAFC);
gp_LEN = gp_prepare_weights(gp_LEN);
// this could probably be faster with a binary search, but we don't bother -
// this whole program is not speed-critical
function gp_choose_random(tab)
{
var max = tab[tab.length-1][0];
var rnd = Math.random()*max;
for (k in tab)
{
var v = tab[k];
if (v[0] >= rnd)
return v[1];
}
// print("**********");
}
function gp_isvowel(s)
{
return s == "a" || s == "e" || s == "i" || s == "o" || s == "u";
}
function gp_starts_with_ei(s)
{
return s.substr(0,1) == "e" || s.substr(0,1) == "i";
}
// Generate a syllab
function gp_generate_syllab(ATAQUE, NUCLEO, CODA, word_so_far)
{
var a, n, c, ok, last, first;
ok = false;
while (! ok)
{
a = gp_choose_random(ATAQUE);
n = gp_choose_random(NUCLEO);
c = gp_choose_random(CODA);
ok = true;
last = word_so_far.substr(-1);
first = a.substr(0,1)
// Define some exclusion rules
if (a == "qu" && !gp_starts_with_ei(n))
ok = false;
else if (last == "s" && (first == "s" || first == "r"))
ok = false;
else if (last == "m" && (first == "r" || first == "l" || first == "t"
|| first == "c" || first == "m" || first == "d"
|| first == "v" || first == "q"))
ok = false;
else if (last == "n" && (first == "b" || first == "p" || first == "n"
|| a == "rr" || a == "ll"))
ok = false;
else if (last == "l" && first == "l")
ok = false;
else if (last == "x" && first == "s")
ok = false;
else if (!gp_isvowel(last) && a == "ñ")
ok = false;
else if (last == "c" && (a == "c" && !gp_starts_with_ei(n)
|| first == "v" || first == "b"))
ok = false;
// TODO: add rll
}
return a + n + c;
}
// Generate a word
function gp_word()
{
var word = ""
var n = parseInt(gp_choose_random(gp_LEN)) + 1;
for (var i = 0; n > i; i++)
{
if (n == 1)
word = gp_generate_syllab(gp_SILABA1A, gp_SILABA1N, gp_SILABA1C, "");
else if (i == 0)
word = gp_generate_syllab(gp_SILABAIA, gp_SILABAIN, gp_SILABAIC, "");
else if (i == n-1)
word = word + gp_generate_syllab(gp_SILABAFA, gp_SILABAFN, gp_SILABAFC, word);
else
word = word + gp_generate_syllab(gp_SILABAMA, gp_SILABAMN, gp_SILABAMC, word);
}
return word;
}
////////////////////// Presentation section
var gp_txt = '\x3Cdiv style="-moz-column-count:2;-webkit-column-count:2;column-count:2;margin-bottom:20px">\x3Csamp id="palabras_generadas">';
for (var gp_i = 0; 30 > gp_i; gp_i++) gp_txt += gp_word() + '\x3Cbr>';
function gp_regen()
{
var txt = ""
for (var i = 0; 30 > i; i++) txt += gp_word() + '\x3Cbr>';
var tgt = document.getElementById('palabras_generadas');
tgt.innerHTML = txt;
}
document.write(gp_txt + '\x3C/samp>\x3C/div>\x3Cbutton onclick="gp_regen();" style="width:100%;margin-bottom:30px">Más\x3C/button>');
-->
</script>
<noscript>
<div style="background:#ffc;padding:0.5ex"><p>Ah, ¿que no tienes JavaScript activo? Oh, vaya, lo siento, pero este programa está escrito en ese lenguaje, así que me temo que no puedes ver las palabras. Quizá si lo activas un momento y recargas... Pero para que te hagas una idea, he aquí una muestra que fue generada al escribir el artículo:
</p>
<div style="-moz-column-count:2;-webkit-column-count:2;column-count:2;margin-bottom:20px"><samp>
prasmerosio<br>
tadnata<br>
una<br>
hanrelaño<br>
ara<br>
onuecacerdesar<br>
crapipas<br>
prade<br>
dupinro<br>
atoco<br>
asetas<br>
menfatamos<br>
toco<br>
sirurplaer<br>
desnasca<br>
fosjo<br>
dolginunria<br>
hodisotas<br>
eje<br>
riecenoa<br>
erpuesecaca<br>
rentusa<br>
sinarfimel<br>
crarda<br>
rilfien<br>
rolsipebes<br>
hacrisioteda<br>
tapesfujere<br>
pendelinzo<br>
masbe</samp></div>
</div>
</noscript>
pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com1tag:blogger.com,1999:blog-7525171767194694125.post-18102149943278570372015-02-23T21:56:00.000+01:002015-03-09T20:25:34.803+01:00Tangerine Tycoon - Fulliverse<div lang="en"><p><a href="http://tangerinetycoon.com/">Tangerine Tycoon</a> is an online Flash game written by Gaz Thomas, where you start by clicking on a tangerine to earn one tangerine per click, and then you can purchase buildings to earn them at a certain rate, given by the number of buildings that you have of each kind. There are four sources of income (tangerines), not counting the "running start" perk: clicking on the tangerine, the buildings, the stock market, and gambling. After you buy at least one of each building, you're given the opportunity to jump to another universe, starting again from zero (but with an increased tangerines-per-second rate for the buildings) in order to score more achievements. It's similar to a bunch of games of the genre, and with a touch of humour. In my case, it was the first I've played through. The current version as of this writing is 1.26. The present analysis is based on that version.
</p>
<p><strong>If you haven't played it, but plan to, better stop reading now, as this post contains spoilers.</strong> You can always get back to this post later.
</p>
<p>The achievements screen has 100 entries that you can unlock as you progress, each adding 1% to your current TpS. Some of them are much harder than others. There is one that is really, really hard to get. The title is "The Fulliverse", and the legend reads: "The universe can not hold any more tangerines". It's unlocked when you reach 10<sup>300</sup> tangerines. That's close to the limit of a 64-bit float, which is about 1.797E+308 (hey, that's a float notation that the game lacks! I hope that's fixed in a later version). The author imposed a limit slightly below that, to avoid having infinite tangerines and being able to buy everything unlimitedly, I presume. With that limit, there's the paradox that some buildings can't be bought; for example, when the number of mutants is between 7055 and 7253, you'll see the price of each between 1.060E+300 and 1.663E+308. Mutants aren't the best example, I know, as you wouldn't buy them anyway, but it's one that you can easily see. At 7254 and above, the price of mutants is displayed as Infinity (the float overflows).
</p>
<p>I'm not going into an analysis of the optimal number of buildings; there is <a href="http://chaosbot.newgrounds.com/news/post/919521">already one</a> except it doesn't specify the optimal strategy about the cat. The base TpS for a cat are 400. The actual TpS (for all, not just the cat) is the base TpS times the percentage resulting from the sum of all the bonuses plus 100 (universe + perks + achievements + 100). You need at least 3 machines to unlock the cat. If you're after all achievements, in the first universes it's always faster to unlock it, and get the achievement corresponding to not unlocking it at a later universe. In that case, you buy 3 machines, letting one of them break, then 5 cats. The time for the cat to appear is random.
</p>
<p>When you have all the Improved Efficiency perks, it's usually faster to buy 18 tangerine machines and aim for the lab, skipping the cat, as unlocking it will happen too late most of the times. I often get my first tangerine lab even before the machine explodes. Since I have the Running Start perk maximized, however, I still unlock it, so it doesn't disturb in the next universe and I can get started faster, improving my times screen. My current best for 50K is 0:14.
</p>
<p>As your TpS rate increases, the strategy towards the Mutants needs to be changed. Your tangerines grow quadratically when you buy the first Mutant (actually it's faster than quadratic until they reach a spawn rate 14, but that doesn't make a significant difference), but that's too slow when your TpS multiplier is high, and it's faster to get 12 ETs and buy a mutant when available, and from there, increase the heroics up to 25 and wait for the first 5D to be available. That's always assuming you don't use anything else than the main (buildings) screen.
</p>
<h4>The Tangerine Exchange</h4>
<p>One of the greatest features of the game is the stock market. There are five commodities all with the same behaviour. You can buy and sell. When you buy, you have to spend either all your tangerines, or half your tangerines; when you sell, you always sell all the stock you have of that commodity. Their prices range between 10 and 100. Each commodity has a trend, which can be increasing or decreasing. When it is increasing, the next price is given by an increment which is a uniformly distributed random number between –3 and +5, and when it is decreasing, it is incremented by a random number between –5 and +3. The <a href="http://en.wikipedia.org/wiki/Expected_value">expected value</a> of the price of a commodity with an increasing trend is thus the previous price + 1 on each market tick (ticks happen every 5.5 seconds more or less), and the previous price – 1 if the trend is decreasing. When the trend is increasing and the price is above 80 (that is, 81 or higher), it keeps the same trend for one more tick and then reverses; the same happens when it's decreasing and the price is below 30 (29 or lower). Due to random fluctuation, it can actually go down to 10 even when the trend is increasing, or up to 100 even when the trend is decreasing. The Insider Trading perks reveal the trends, one for each commodity, but you can also figure it out by watching the increments or decrements of the price: if it increases by 4 or 5, the trend is increasing, and if it decreases by 4 or 5, the trend is decreasing. That happens every 4.5 ticks on average.
</p>
<p>The following PHP program demonstrates the behaviour of the Tangerine Exchange (the signs indicate the trend, they do not apply to the value):</p>
<pre class="php"><?php
<i>function</i> MarketTick<i>(&</i>$prices<i>, &</i>$trends<i>)
{
for (</i>$i <i>=</i> 0<i>;</i> $i <i><</i> 5<i>;</i> $i<i>++)
{</i>
$trend <i>=</i> $trends<i>[</i>$i<i>];
if (</i>$trend <i><</i> 0 <i>&&</i> $prices<i>[</i>$i<i>] <</i> 30<i>)</i>
$trends<i>[</i>$i<i>] =</i> 1<i>;
else if (</i>$trend <i>></i> 0 <i>&&</i> $prices<i>[</i>$i<i>] ></i> 80<i>)</i>
$trends<i>[</i>$i<i>] = -</i>1<i>;
do
{</i>
$newprice <i>=</i> $prices<i>[</i>$i<i>] +</i> $trend <i>+</i> mt_rand<i>(-</i>4<i>,</i> 4<i>);
}
while (</i>$newprice <i><</i> 10 <i>||</i> $newprice <i>></i> 100<i>);</i>
$prices<i>[</i>$i<i>] =</i> $newprice<i>;
}
}</i>
$prices <i>= array();</i>
$trends <i>= array();
for (</i>$i <i>=</i> 0<i>;</i> $i <i><</i> 5<i>;</i> $i<i>++)
{</i>
$prices<i>[</i>$i<i>] =</i> mt_rand<i>(</i>30<i>,</i> 80<i>);</i>
$trends<i>[</i>$i<i>] =</i> mt_rand<i>(</i>0<i>,</i> 1<i>) *</i> 2 <i>-</i> 1<i>;
}
do
{</i>
$oldtrends <i>=</i> $trends<i>;</i>
MarketTick<i>(</i>$prices<i>,</i> $trends<i>);
for (</i>$i <i>=</i> 0<i>;</i> $i <i><</i> 5<i>; ++</i>$i<i>)</i>
printf<i>(</i><b>" %+4d"</b><i>,</i> $prices<i>[</i>$i<i>] *</i> $oldtrends<i>[</i>$i<i>]);
echo</i> <b>"\n"</b><i>;</i>
usleep<i>(</i>5500000<i>);
}
while (</i>true<i>);</i>
</pre>
<p>Trading at low prices has the advantage that the increments imply a higher percentage than at high prices; for example, an increase from 10 to 11 is a 10% gain (if you bought at 10), but from 99 to 100 it's about 1% gain (if you bought at 99). Buying at above 80 is not recommended because there's no guarantee that you will sell above that price without at least one full decrease-increase round. 80 itself is still safe, assuming it was never above 80 for this run.
</p>
<h4>Gambling</h4>
<p>Trading is helpful, especially at the beginning, but for reaching a Fulliverse achievement, it's too slow. Buying at 25 (a safe-ish price without missing that train) and going up to 50 then repeating, assuming there's another commodity immediately available at 25, would take you an average of more than two minutes, to just duplicate your gains. That's about 7 minutes to multiply your gains by 10. You can be lucky at times and grab a faster train (e.g. 16 happens relatively often) to triplicate or even quadruplicate your gains, but that doesn't happen often enough as to be significant. Fortunately, it's possible to go faster than that by gambling.
</p>
<p>Gambling is a double-or-nothing game: you either lose all you have, or get double of what you have. With 50% probability, the expected value of every game is to stay as you are; however, for a repeated game the probability of losing everything increases exponentially as you play, so the strategy of just playing repeatedly obviously leads nowhere, unless the probability of losing once is less than the probability of winning 1000 times in a row, which obviously doesn't happen with this game's limits.
</p>
<p>You can increase your chance of winning in two ways: by buying it from the gambling screen (you can buy up to 15 increases; each costs 10x the previous and adds 1% to your chances), and by perks (each perk adds 1%, up to 10 times). At the maximum, the chance of winning is 75%.
</p>
<p>How to gamble without losing everything? This part gets tricky. The trick is to use the "Purchase Volume: 50%" feature in the Tangerine Exchange so that you don't lose all you have, using it as a kind of bank. The Insider Trading perks help here, as you can use trending info to ensure that your investment will not result in a loss in average.
</p>
<p>There is a possibility of using <a href="http://en.wikipedia.org/wiki/Martingale_%28betting_system%29">martingale</a> as a strategy, betting the base amount after each win and using the stock market to double your bet on each loss, as described in the <a href="http://chaosbot.newgrounds.com/news/post/919521" rel="nofollow">same post linked before</a>. That's a poor strategy in this case, as it only grows your gains linearly, barely taking advantage of the increased win rate, and making it exponentially slow to double your tangerines.
</p>
<p>Here's a better strategy. Choose a commodity with an increasing trend, and buy 50%. Go to the Gambling screen, and play. Whatever the outcome, go back to the market, and sell. Rinse, repeat. With careful timing, it's even possible to do it without the value changing, by waiting for the tick to happen between selling and buying, enabling us to use the strategy without caring about the trend.
</p>
<p>The analysis of this strategy turns out to be somewhat difficult. At first I did a mistake calculating it. First, if your initial amount of tangerines is <span class="math"><i>n</i></span>, you invest <span class="math"><i>n</i>/2</span> in the Exchange, and gamble the other <span class="math"><i>n</i>/2</span> for double or nothing. Thus if you lose and sell your stock (disregarding gains or loses coming from the stock market), you end up with <span class="math"><i>n</i>/2</span>; and if you win and immediately sell your stock, you end up with <span class="math">2·(<i>n</i>/2)+<i>n</i>/2 = 1.5·<i>n</i></span> tangerines. The expected value for every game when the chance of winning is <span class="math"><i>p</i></span> is therefore <span class="math"><i>p</i>·1.5·<i>n</i>+(1–<i>p</i>)·0.5·<i>n</i> = <i>n</i>·(<i>p</i>+0.5)</span>. Therefore, the expected value for a 75% win probability is <span class="math"><i>n</i>·1.25</span>, so our gains will be multiplied by 1.25 after every game, right?
</p>
<p>Wrong! It's not so easy. With 75% probability, the win rate is 3 out of 4, so if your gains get multiplied by 1.5 when you win and by 0.5 when you lose, after 4 games your gains will be on average multiplied by <span class="math">1.5·1.5·1.5·0.5 = 1.6875</span>. That's not the same as <span class="math">1.25·1.25·1.25·1.25 = 2.44140625</span> which would be the result if we followed the other reasoning. The actual average multiplier per game for a probability of winning <span class="math"><i>p</i></span> is <span class="math">1.5 <sup><i>p</i></sup>·0.5<sup>1–<i>p</i></sup></span>, which for <span class="math"><i>p</i> = 0.75</span> equals <span class="math">1.1397535</span> approximately.
</p>
<p>With that in hand, we can calculate the minimum percentage at which to try this strategy. Note that, against intuition, it doesn't work for <span class="math"><i>p</i> = 0.5</span>. The expected multiplier for each game with a 50% chance of winning is approximately 0.866025 (it's exactly <span class="math">0.75<sup>0.5</sup></span>), therefore you end up losing more than 13% per game on average. Starting with a 64% winning chance, things get better. For 64%, the expected multiplier is approximately 1.01, so you win 1% on average. With 63% or less, you always lose something. I wouldn't risk on a 1% winning probability, though. It would take a huge amount of bets for that 1% to be noticeable. It takes about 231 attempts on average to just double your gains; in the short term, bad streaks can ruin you.
</p>
<p>So, for a 75% winning chance, which is the maximum, what is the expected number of games needed to multiply your savings by 10? That's obtained by solving for <span class="math"><i>x</i></span> in <span class="math">1.1397535...<sup><i>x</i></sup> = 10</span>, whose solution is <span class="math"><i>x</i> = log(10)/log(1.1397535...)</span> which is approximately equal to 17.6. And to double them? The expected number of games is then slightly less than 5.3. If you time your games with the Exchange ticks as described above, then the time it takes to double your gains is almost 5 times faster when compared to the winnings that you would get using the Exchange alone in the optimistic scenario explained earlier.
</p>
<p>Is it possible to do better than that? That's not easy to answer, but I think so. A different strategy is to gamble twice in a row each time, before selling your stock and buying again. At first sight it may seem to not make a significant difference, but once we make the calculations, reality tells us otherwise.
</p>
<p>The probability of winning twice in a row for a winning probability of <span class="math"><i>p</i></span> is <span class="math"><i>p</i>²</span>. For <span class="math"><i>p</i> = 0.75</span> that probability is <span class="math">0.75² = 0.5625</span>, so more often than not you win twice in a row. Each time you lose you still get <span class="math"><i>n</i>/2</span> because your other half is safe during both games. But if you win both games, your total after selling your stock is <span class="math">2·2·(<i>n</i>/2)+<i>n</i>/2 = 2.5·<i>n</i></span>.
</p>
<p>So what's the expected multiplier after each pair of games? In this case, it's <span class="math">2.5 <sup><i>p²</i></sup>·0.5<sup>1–<i>p²</i></sup></span>, which for <span class="math"><i>p</i> = 0.75</span> equals 1.23635 approximately. Is that clearly better, or are the gains negligible with respect to the previous method? The answer seems to lie somewhere in between. The number of attempts it takes to double your gains is 3.26, which is more than half the 5.3 times it took with the previous method. And since it takes two gambles every time, it takes longer to play, but switching back and forth to the market also adds a constant time independent of how many times you gamble, reducing the relative importance of the increased number of games. So, does one thing compensate the other? I think it <i>feels</i> faster, but that's a personal appreciation and not based on quantitative data, which was too difficult to gather for me. The minimum percentage to make this system return gains raises to 66%.
</p>
<p>Note that when you lose the first of the two gambles, you don't need to play the second. That's a timing advantage. Does that introduce bias? I don't think so, but I haven't really thought it through. Also, with this method you can forget about coordinating it with the Exchange ticks, because there isn't enough time. You need to use a commodity with an increasing trend, and hope for some luck. That's kind of a timing advantage too, because coordinating it takes time (to wait for the expected tick). Choosing an increasing trend is important, and more so when losing at gambling. After a win, the market percentage is applied over the <span class="math"><i>n</i>/2</span> that was invested in the Exchange; the <span class="math">2·<i>n</i></span> from the gambling is not affected, therefore the percentage affects only 1/5 of your final tangerines. But when you lose, it affects the total you have remaining. For example, after a decrease in a commodity price from 30 to 27, if you lose that's a 10% decrease, but if you win the overall loss is only 2%. I only waited for the price to raise again when I lost and the Exchange price was low.
</p>
<p>And what about a strategy of gambling three times in a row, then? Let's see. The multiplier per win is <span class="math">4.5·<i>n</i></span>, and per loss it's the usual <span class="math"><i>n</i>/2</span>. The chance of winning three times in a row is <span class="math"><i>p</i>³</span>, which equals 0.421875 when <span class="math"><i>p</i> = 0.75</span>. The average multiplier for that <span class="math"><i>p</i></span> is <span class="math">4.5 <sup><i>p</i>³</sup>·0.5<sup>1–<i>p</i>³</sup> = 4.5<sup>0.421875</sup>·0.5<sup>0.578125</sup> ≅ 1.2634</span>. That takes about 2.96 cycles to double your gains, and 9.85 to multiply them by a factor of ten. I say negligible when compared to the 10.85 it takes with the two in a row method, considering you have to gamble once more, which also takes its time. The minimum percentage for it to start to be profitable is 69%.
</p>
<p>And with a strategy of four times in a row? With the game's limit of 75%, it's not worth it. The gains per cycle for 75% are about 1.2254, which is <em>lower</em> than with the two in a row method. It starts to be profitable at 71%.
</p>
<p>So I stuck with the twice in a row strategy to reach that achievement. How many cycles does it take? For a starting point of 1E+75 (reasonable when the Upgod perk is maximized), you need to multiply your gains by 1E+300/1E+75 = 1E+225. Solving for <span class="math"><i>x</i></span> in <span class="math">1.23635...<sup><i>x</i></sup> = 1E+225</span> gives us an average number of trials <span class="math"><i>x</i></span> of approximately 2441. So it's still a long trip. My current best for 1E+100 is 30:14 (of them, 6:29 were used to get to the 5D's).
</p>
<p>But it was fun. The best of it was to figure out all this, of course. I noticed my mistake by running the following simple program. I expected the result to be around 3096 (since 1.25<sup>3096</sup> equals 1.07E+300), but it was actually above 5000 most often, and never less than 4700. The actual expected number of games with the first strategy and a starting amount of 1 is about 5281, and unsurprisingly, the program output is around that.
</p>
<pre class="php"><?php
$count <i>=</i> 0<i>;</i>
$money <i>=</i> 1<i>;
do
{
if (</i>mt_rand<i>(</i>0<i>,</i> 99<i>) <</i> 75<i>)</i>
$money <i>*=</i> 1.5<i>;
else</i>
$money <i>*=</i> 0.5<i>;</i>
$count<i>++;
}
while (</i>$money <i><</i> 1e300<i>);
echo</i> <b>"It took</b> $count<b> attempts to reach the goal.\n"</b><i>;</i>
</pre>
<p>What other strategies are there? Well, I haven't done the math, but there are some more that come to mind. One is to invest 50% in one commodity, and 50% of the remainder in another, so the total invested is 75% and you only gamble 25%. You can then choose how many times to gamble as above. Another more risky one is to do the same, but then sell the 50% one before gambling. Then you're gambling 75% and keeping 25% safe. And there are other possible strategies that don't involve selling the previous stock when you win, but to buy from a different stock, possibly selling the previous one afterwards. I'd like to know if someone does an analysis of them and finds one that gives better results than the ones presented here, or if someone presents some other strategy.
</p>
<h4>Acknowledgements</h4>
<p>I am grateful to Otto Nyberg for pointing out where my error was and leading me in the right direction.
</p>
</div>pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com0tag:blogger.com,1999:blog-7525171767194694125.post-17281703603248627682014-12-26T02:37:00.000+01:002017-04-03T23:44:05.009+02:00Unicursal mazes and "Cavern" mazes<h4>Mazes: Generalities</h4>
<p>There are many kinds of mazes, and many ways to classify them. Some mazes have loops, but most don't. Most mazes have no inaccessible areas, and are usually drawn on a square grid. The mazes that meet the requirements of having no inaccessible areas (i.e. all passages are connected) and no loops are called <em>perfect</em> mazes.
</p>
<p>Mazes are intimately related to graph theory. The most common type of maze can be represented by an undirected graph (but there are also many other types of mazes which need a directed graph to be represented, including arrow mazes and "mazes with rules"), it has no loops (and is therefore a tree) and has every room (vertex) connected to all others. In graph theory terms, that's exactly what a spanning tree is. In maze jargon, that's called a perfect maze. In other words, the terms perfect maze and spanning tree are equivalent, in their respective contexts.
</p>
<p>Perfect mazes are the most common type because they meet two desirable properties. First, since there are no loops, the solution (the route from a given vertex labeled "start" to another given vertex labeled "end") is unique, not counting backtracking from a dead end. Second, since they don't have any unreachable areas, they make the most use of the available space (usually a rectangle). Actually, I personally think that the no loop condition makes them kind of boring, because it makes them trivial to solve (by wall-following). But this article is not about solving difficulty. It has more to do with aesthetics and graph theory.
</p>
<h4>Representing a maze</h4>
<p>For a compact representation, two-dimensional mazes are usually represented in a bitmap by having the vertices (rooms, in maze jargon) at odd-numbered rows and columns (assuming the first row and column is 0, as we're usually dealing with bitmap coordinates), then having some cells that are always walls and some cells that may or may not be walls, depending on whether the corresponding graph has an edge at that position (passage) or not (wall).
</p>
<div class="center">
<div><img border="0" alt="2D grid graph vs. a compact bitmap template" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj05nxF3GqmSTObzC26HnyUtkx84nJyF_9jZs1p96CM1MGnI7_4BMmArKA2pwe90bQ5jqZP6_QeREs3TnKHF00oZtyGyaezJztA4edML4cibKl9kVAwXQbN8gBc2ssvNEcfSmODJUyee0U/s331/GridGraphVsMaze.gif" /></div>
<div class="caption">12×12 grid graph as is commonly represented in a compact way in a cell grid. The white cells are always walls, the black cells are always empty, and the blue cells represent pixels that are to be turned into white or black to give shape to the maze. In the corresponding grid graph, the blue cells represent the edges, the black cells represent the vertices, and the white cells are empty spaces. The cross-faded image depicts the graph itself for comparison. A perfect maze is a spanning tree of that graph.
</div>
</div>
<p>There's another common way of depicting mazes, using squares with missing sides for the rooms, where the present sides are the walls. But we're not interested in it now.
</p>
<p>Observe that a maze constructed using that kind of template will always have the passages and the walls one pixel wide, and that there won't be any walls or passages touching only diagonally. In other words, there are three 2×2 patterns that no 2×2 subset of the bitmap will match: all walls, all passages, and a checkerboard pattern. That's guaranteed by the fact that every single 2×2 subset of the bitmap will always have at least one black cell in one corner (the vertex) and one white cell in the opposite corner (the empty space), regardless of the values of the other two.
</p>
<div class="center">
<div><img border="0" alt="Excluded patterns vs. all patterns" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg8a5hCqyB9CCNYK_5UP4PwqWBaQsaYObjGctBtcIL23puUkbEsyOBfT_6DVV0KB3ysrasc_pcOZJA7wNHQLd34tNFllaMV5XrNf9TWn9IxZ9kJPzbytq3kjGyXGTUzzYYIM-aBrqbeJag/s432/ExcludedPatterns.png" /></div>
<div class="caption">The top row shows the 2×2 patterns that will never appear in any 2×2 subset of the maze image (2×2 wall, 2×2 passage, and 2×2 checkerboard in its two variants). The rest of the patterns can appear anywhere.
</div>
</div>
<h4>Unicursal mazes</h4>
<p>There is a kind of maze that is... well, hardly a maze actually. It's called a unicursal maze, which is a maze with no dead ends and no loops. That's right: it is just a single path that can be followed from the beginning to the end without finding any intersections in the way. Sounds dull? Keep reading!
</p>
<p>A unicursal maze is not necessarily perfect: it may not meet the condition of going through all possible rooms. Obviously, a perfect unicursal maze is a Hamiltonian path in graph theory terms: a path that goes from one point to another, visiting every vertex in the graph exactly once. This equivalence has at least two major implications. First, we can't always have a perfect unicursal maze with any arbitrary starting point and any arbitrary ending point, even in a rectangular grid graph. Second, creating a perfect unicursal maze with chosen endpoints is not a trivial task, if possible at all. Turns out unicursal mazes aren't that dull, after all!
</p>
<p>Let's quickly review the conditions that the endpoints must meet, in order to be possible to have a perfect unicursal maze. The maze dimensions should both be 4 or more, or 3×(odd number ≥ 3), to exclude some special cases that are hard to handle, as some have a solution and some don't. The rest of the conditions depend on whether the associated rectangular grid graph has an odd number or an even number of vertices. If the number is even, then the only condition is that the sum of row + column of the starting point and the sum of row + column of the ending point must have different parity. If the number of vertices is odd, the parity of the endpoints must be the same, but there is another condition: they also need to have the same parity as the vertices in the corners. There's a demonstration in <a href="http://www.cs.technion.ac.il/~itai/publications/Algorithms/Hamilton-paths.pdf">[1]</a>. If a rectangular grid graph with the required dimensions meets these conditions, it's guaranteed that at least one Hamiltonian path exists between both. If not, it's guaranteed that none exists. In other words, the conditions are necessary and sufficient for those grid graphs. The conditions for 2×n, 1×n, and 3×(even number) are too complex for this quick synopsis; take a look at the paper if interested. Of course, what is said for m×n is valid also for n×m.
</p>
<h4>Creating perfect unicursal mazes</h4>
<p>So, how to create a random perfect unicursal maze? One possible way is to create a Hamiltonian cycle and break it, using the split points as start and end. This obviously reduces the problem to creating a Hamiltonian cycle. A trick used to create such a cycle consists of applying the following concept: create a regular maze, and then add a wall in the middle of each passage.
</p>
<div class="center">
<div><img border="0" alt="Spanning tree and Hamiltonian loop" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKTRnxsBM3-5tQnug4VzBWdFVvEVgMwuk4T_STtgrsWrZor2WGoB06ZdU83GXjQWQmjlqbbGgCyGgeCo6FmL6-7nKcQtomVZBhMHUJyxRxwEjVxIr5qG13pDe1XATBOeba6fGI6Gf14qw/s410/UnicursalFromSpanningTree.png" /></div>
<div class="caption">Perfect maze (left) and unicursal loop resulting from it (right). The loop can be broken at any edge next to one of the exterior walls and parallel to it (marked in red), to give a unicursal maze that starts and ends at a border.
</div>
</div>
<p>But that method creates unicursal mazes with a very clear pattern: every single-step edge (in the sense of not turning while walking it) is preceded and followed by two-or-more-steps edges. That's certainly not what one expects from a random Hamiltonian path with any signs of uniformity. It is only able to generate a small subset of the possible Hamiltonian paths. Even if the algorithm is modified to enable arbitrary starting and ending points (arbitrary among the ones that meet the necessary conditions for a Hamiltonian path to be possible), that problem will still exist with that method. Is there an algorithm that allows for the whole range of possibilities of Hamiltonian paths?
</p>
<p>Fortunately, yes. Not exactly uniformly distributed, but not too far either. It's capable of generating every possible Hamiltonian path, with a probability distribution that is empirically not distinguishable from a uniform distribution. The paper describing the method and the empirical results is <a href="http://arxiv.org/abs/cond-mat/0508094">[2]</a>, and to the surprise of many, including me, it's about polymers. Yes, the molecules. It turns out that random Hamiltonian paths have an important role in the field of polymer chemistry. Live and learn.
</p>
<p>Let me outline the method. Start with a non-random, easy to build Hamiltonian path, consisting of going in zig-zag from one end of the top row to one end of the bottom row. Actually, the starting pattern does not matter much, but that's an easy way to build one in a rectangular grid graph. Then repeatedly apply a transformation of that path that they call "backbite", using one of the two endpoints at random every time. A backbite transforms a Hamiltonian path into a different one that is also Hamiltonian, and it consists of the following: from one endpoint, choose a neighbouring vertex at random that is not connected to the current one. If the chosen vertex turned out to be the opposite endpoint, add an edge to it and remove the edge that it was connected to originally. If it is not the opposite endpoint, add an edge that connects to it. That will create a loop for sure, and the destination vertex will now have three edges. One of them will be the one just created and another one will lead to the opposite endpoint. The third edge loops back to itself. Remove it to obtain a graph that is again a Hamiltonian path, but with a different endpoint. That's the backbite move.
</p>
<div class="center">
<div><img border="0" alt="Backbite move illustration" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOmI2RYJ1K-eJCNyiTfSurqCwoah7JomxPmsw8L3trXA9upVtPnt7OGtEysFn7RGLl1WHwfXJFompAb8s88eiPPap7CzzA_egHnVf9REWbGdUByPkGFk8DEPLE8vW-D8ap0xBxUAfE_uA/s547/Backbite.png" /></div>
<div class="caption">Backbite move on a 3×3 graph. Starting with a Hamiltonian path (1), choose an endpoint (red in this case) and a neighbouring vertex at random that is not connected to that endpoint. In this case there's only one, so an edge is added that connects to it (2, in green). The target vertex (3, in green) has two other edges apart from the one just added: one leading to the opposite end (blue) and another looping to ourselves (red). Complete the move by removing the latter, resulting in another Hamiltonian path (4) with a new endpoint (in red).
</div>
</div>
<p>By repeating the backbite move enough times, the authors of the paper show how the resulting path is approximately uniformly distributed, according to several criteria.
</p>
<p>This method has two main disadvantages. First, it's not easy to determine when to stop, and given that the backbite move is relatively expensive, making many more moves than necessary to be in the safe side is slow. Second, both endpoints end up at unknown locations.
</p>
<p>Another method that also uses the backbite move, is to perform a (non-overlapping) random walk starting from one chosen point, and when it is not possible to go on because all neighbours are already visited, perform one single backbite (on the blocked end only) and then try again the random walk. (One rare exceptional case: if the backbite move leads to the original endpoint, switch endpoints, as we want one of them to be at a fixed location.) Repeat the process indefinitely. Eventually, if the starting endpoint was chosen so that it obeys the conditions, we will obtain a Hamiltonian path with a known start and a random end. A series of backbites on that end can help us now place the endpoint at a desired location (always chosen by the rules). That path may not be uniform, but visually it's hard to tell the difference. The main advantage is that it has better guarantees that all edges are visited and randomized. With the other method, some areas may remain unvisited by the repeated backbite and therefore left in their original state.
</p>
<div class="center">
<div><img border="0" alt="Unicursal maze (Hamiltonian path) and unicursal loop (Hamiltonian cycle)" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPgRQCfCC0IKeOwyDElPdGZpLFu2mkPz0KekFG0o2EeU1pqByRerBqA0UjzFG1ZyFprJKVhKl3Wjd53cDaGalzOdxVSn-1vO667jHQxYn33gLleZhz4ezvsU03P1_brrO5SLR_948Moqw/s634/UnicursalMazeLoop.png" /></div>
<div class="caption">12×12 perfect unicursal maze (Hamiltonian path) and 12×12 perfect unicursal loop (Hamiltonian cycle) created with the technique described.
</div>
</div>
<h4>Cavern mazes</h4>
<p>It is possible to construct mazes that do not obey the pattern in the first figure, that is, mazes whose walls are not aligned in a grid of the kind described. Every pixel would be a vertex, and every pair of passage pixels vertically or horizontally adjacent would be connected by an edge. These mazes are no longer spanning trees of a grid graph, as there are many disconnected vertices (one per wall pixel), but compared to the perfect mazes we've seen so far, they share the characteristics of having no isolated connected areas or loops and being trees, so we'll extend the concept of "perfect maze" to also include those that have isolated vertices, for discussion purposes. Walter D. Pullen <a href="http://www.astrolog.org/labyrnth/daedalus/daedalus.htm">[3]</a> calls those "cavern" mazes, and we're using the same term here for lack of any others. There are several ways to construct them. Usually, not all the 2×2 exclusion patterns mentioned earlier are absent in the mazes created this way; most of the times, there are 2×2 checkerboards and/or 2×2 walls and/or 2×2 passages at some points.
</p>
<p>Let's take as an example the maze generated by the good old ZX81 program <em>Mazogs</em>. <em>Mazogs</em> is a game in which you are in a 62×46 dungeon or cavern, with passages organized like a maze, and your aim is to search for the hidden treasure in the cavern with the help of the prisoners scattered around the maze, avoiding to run into a Mazog (or if you do, you better hava a sword with you; swords are also scattered around), then return to the entrance. It had a very well done atmosphere at the time (1982). You really felt like it was a cavern with dark passages. And part of the ambience was provided by the fact that the maze did not look too geometric, but more like random passages in a real cavern. When its successor, <em>Maziacs</em>, came for the ZX Spectrum, I was disappointed because it lost that feeling due to the change in the style of maze: it was of the kind described at the beginning, and thus no longer a cavern maze. It lost the sensation of being dungeon-ish, and felt more geometric due to the longer passages. The added complications didn't really improve the gameplay in my opinion. But the point here is that the shape of the maze did have an important role in the overall feeling of the game. The name "cavern" is well deserved.
</p>
<p>One characteristic of the mazes created by <em>Mazogs</em> is that it could easily create 2×2 checkerboard blocks, as well as wall-only 2×2 blocks, but never 2×2 passage blocks.
</p>
<div class="center">
<div><img border="0" alt="Mazogs maze" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggL-TOMuP87ID__iFacOnmceEnCCynVcYJaqJFpXJAQOJjnzmigIdSRooTPkSWht2y_Om6sBlHUx8cAvKgbFLNTUUNS3_NMYKf5MMNeZd3o9cKfoSG8GOUERIzPRJt228s6KJOjeiXnnQ/s512/MazogsMaze.png" /></div>
<div class="caption">Example of a maze generated by <em>Mazogs</em>. "1" is the player (at the starting point in this case), "T" is the treasure, "X" are mazogs, "S" are swords, "P" are prisoners that reveal the solution when run into. The generation method is a "hunt and kill" variant, where the hunt phase is random rather than sequential, and the kill phase considers whether there are passages next to the candidate to be carved, and carves one cell at a time. The program has a time limit for generating the maze, and a minimal amount of passages for it to be considered valid. If that minimum is not met, the algorithm restarts.
</div>
</div>
<p>Pullen has a program for Windows, called <em>Daedalus</em>, that is able to generate cavern mazes with no 2×2 checkerboards, and you can decide whether you want no 2×2 passages, or no 2×2 walls, but not both (unless you're really, really lucky and get one by chance). Daedalus runs under <em>Wine</em> although with some flicker, at least in my system.
</p>
<div class="center">
<div><img border="0" alt="Daedalus Cavern maze with 2×2 walls (left) and 2×2 passages (right)" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwinuS6d47Z6XRvZo0ko3zSy_CJCC8awLhN71UV1ABYW5TMGyZKXsdPfMQWyuzVe2PMG4gWI7j0jgXoJW4jyZxH3eDFZ2Qkd1vuFudAEi5nQEjW3bLfdw3YFtxM4AwAY-f_MW6mlDVXis/s636/CavernMazeDaedalus.png" /></div>
<div class="caption">Cavern mazes generated by Daedalus. Neither has a 2×2 checkerboard pattern (passages touching diagonally). The one on the left was generated by passage carving, and has 2×2 walls. The one on the right was generated by wall adding, and has 2×2 passages. The scarceness of walls that start at the border in the right image (there are long passages that run throughout most of the side walls) is typical in this program when generating a cavern maze this way.
</div>
</div>
<p>Is it possible to generate cavern mazes that are guaranteed to meet all three criteria (no 2×2 checkerboard, walls, or passages)? The answer is of course yes, but here comes the funny part: the problem of generating such a maze is equivalent to generating a Hamiltonian cycle in a grid graph.
</p>
<p>How's that possible? Note the frontier between the wall and the passage, imagining it as a graph. Having a 2×2 checkerboard pattern somewhere, would mean we have a vertex with four edges converging to it, but that's excluded. Having a 2×2 wall or passage would mean that one vertex has no edges leading to it, but these patterns are also excluded. Every other combination of 2×2 cells has a frontier that has exactly 2 edges, therefore every vertex of that graph has exactly 2 edges connecting it. That means that it must form loops that pass through every vertex in the graph. But since it encloses the passage area, which is (by definition of a perfect maze) one single area, it can't form more than one loop. Consequently, it is one single loop that passes through all vertices, i.e. a Hamiltonian cycle. Effectively, this process is equivalent to doing the reverse operation to adding a wall in between the passages, that we mentioned as one method of creating a perfect unicursal maze. In this case, since the loop encloses a wall, we remove it.
</p>
<div class="center" style="margin-bottom: 1ex">
<div><img border="0" alt="13×13 Cavern maze resulting from the 12×12 Hamiltonian cycle shown before" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhrp8IEqCST19FFOXOeuZQDnpTdbbCWumj-3R5VzuX_OUbCjLKvWT7US2WJv46fcMuYe0YAY9qWHk3rA8SfvUzmC59qy8Wl9lYV-cqmKIWUAtldDNW3wJc4vJHVQC7zPQr-ZwjB68wlyM/s156/HamiltonianCavern.png" /></div>
<div class="caption">This is a 13×13 cavern maze generated by using the 12×12 Hamiltonian cycle shown earlier as the frontier of the walls. It has no 2×2 checkerboard, wall or passage block.
</div>
</div>
<div class="center" style="margin-bottom: 1ex">
<div><img border="0" alt="Graph for the cavern maze above" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjygMdFjLDU6QiFlJFkSDUcUD6-4vD7Mc5Vk4EvoK9v5qYYcB8roknE3ZJI1kbrcTjExF-9c_DgTzszWiz6yVy4PMpoVAlGRpGjqWUSdD0sUAwybXjttboELkwlLVFXw3HjcdY9D152Gdo/s276/EquivalentGraph.png" /></div>
<div class="caption">Equivalent graph for the cavern maze above. The red dots represent unconnected vertices.
</div>
</div>
<div class="center">
<div><img border="0" alt="65×65 cavern maze" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyf3OjEFQoYv3xmVAVFZhltpDF-tWBBlWsSH7-rnW7oNxIiglhj9nJEWxL6VKmZ32pjN4EU_79yYfLXvyM6cgp1G-t0gfuDpTcs6HjbwWo8Z2Y1aNsEbANVKf71IP_hEQGW1Tvpsn3SxY/s520/HamiltonianCavern65.png" /></div>
<div class="caption">65×65 cavern maze generated from a 64×64 Hamiltonian cycle.
</div>
</div>
<p>Cavern mazes generated this way look quite nice in my opinion. Of course that's a question of taste, so YMMV. One problem they have, however, is that, just like with uniform random spanning trees, they tend to have lots of short passages, instead of being primarily made of long winding ones that would play a better role in confusing the player. So far I've found no solution for this, but a promising idea is to start by building a cavern maze with the desired properties except for the 2×2 wall blocks, then modify it to make these blocks disappear. It's not easy to do the latter; it's impossible to make just one single block disappear (other than by creating a 2×2 passage block, which obviously isn't the desired result). However, it's possible to use two such blocks so that with local modifications in between, they cancel each other. So far I haven't figured out an algorithm that is able to do that, but I've been able to do it by hand. Not all pairs of 2×2 blocks can be cancelled this way; they have to be of opposite parity (the parity of such a block is the parity of its central vertex, when seen as the frontier graph, obtained from the sum of its row and column, modulo 2).
</p>
<div class="center">
<div><img border="0" alt="Cancelling blocks example" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtP_eBNSmSm7SZxhXkyQm4OnfafM1AzFVRtHxBVrcy9nyixKg0QnmkUH9b3bl80nPQ-239FYJv2h-cxh1jQK3o3iLzHdGXJG1z-T4V8YYqWCksgQScUEGruzhTcldfk48omiY5LV5C55E/s426/CancellingBlocks.png" /></div>
<div class="caption">Example of a section of a maze that has two 2×2 wall blocks (left) with the parity of the vertices marked with green and yellow dots, showing that the blocks have different parity. When that's the case, it's possible to modify some pixels in such a way that the blocks cancel each other and disappear. The image on the right shows one of many possible ways to do it.
</div>
</div>
<p>Is there a move, similar to the backbite, that allows displacing an isolated vertex of a certain parity to another position without changing any characteristics of the graph? If so, joining vertices of opposite parity should not be difficult: when they are next to each other, usually one pixel change suffices to cancel both. I'd be interested in knowing about an algorithm that can do that.
</p>
<h4>Update (2017-04-03)</h4>
<p>I forgot to mention that in order for a rectangular grid graph to have a Hamiltonian cycle, it must have an even number of vertices, therefore rectangles with an odd number of vertices on each side can't have one. The reason should be obvious when considering that any Hamiltonian cycle can be obtained from a Hamiltonian path that has the two endpoints adjacent, then adding an edge between them to close the loop. Adjacent vertices always have opposite parity, and when the number of vertices is odd, they must have the same parity, therefore they can't be adjacent, therefore they can't form a loop.
</p>
<h4>References</h4>
<p class="noindent">[1] Alon Itai, Christos H. Papadimitriou and Jayme Luiz Szwarcfiter, <a href="http://www.cs.technion.ac.il/~itai/publications/Algorithms/Hamilton-paths.pdf">Hamilton paths in grid graphs</a>. <i>SIAM Journal of Computing,</i> 11(4):676—686, 1982.
<br />[2] Richard Oberdorf, Allison Ferguson, Jesper L. Jacobsen and Jané Kondev: <a href="http://arxiv.org/abs/cond-mat/0508094">Secondary Structures in Long Compact Polymers</a> (2008) (preprint).
<br />[3] <a href="http://www.astrolog.org/labyrnth/daedalus/daedalus.htm">http://www.astrolog.org/labyrnth/daedalus/daedalus.htm</a>
</p>
pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com3tag:blogger.com,1999:blog-7525171767194694125.post-87214179611839967462013-08-07T22:50:00.000+02:002013-08-12T11:20:31.901+02:00Inside-out Fisher-Yates algorithm<p>The inside-out Fisher-Yates shuffling algorithm looks like this:
</p>
<pre class="code">
<i>outputlength</i> = 0
<b>while</b> there are input elements:
<i>r</i> := random number between 0 and <i>outputlength</i> inclusive
<b>if</b> <i>r</i> <> <i>outputlength</i>:
<i>output</i>[<i>outputlength</i>] := <i>output</i>[<i>r</i>]
<i>output</i>[<i>r</i>] := next input element
<i>outputlength</i> := <i>outputlength</i> + 1
</pre>
<p>Here's an animation that shows it in action:
</p>
<div class="separator" style="clear: both; text-align: center;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpimtwdKgMV_QTI0UU12m3NIO0FUZsryqiK4MuYfBO6hl4ED-jXy2Ya3Goc5cS1GeggIvNWDM6MzJQexqSBqFOu4HC8d0dRTxyPhYnxhscQd640YPErFMLjVk9GVscaH4dZzhRYUB3FCI/s1600/FisherYatesAnim.gif" /></div>
<p>For artistic purposes, the animation shows how the "pushed" element goes to the end of the array after the position where it was originally is replaced by the incoming element. In the algorithm, that step actually precedes the replacement.
</p>pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com0tag:blogger.com,1999:blog-7525171767194694125.post-9164541361763660242013-05-11T14:28:00.000+02:002013-08-12T11:48:43.951+02:00From cryptoloop to dm-crypt in Debian<p>I've been struggling with it for a while and finally found the solution. I was using <code>cryptoloop</code> in <i>Lenny</i> and needed to migrate to <i>Squeeze,</i> from which <code>cryptoloop</code> is removed. The tutorials tell me to just use <code>cryptsetup</code>, but none of them mentions one important detail.
</p>
<p>From the <a href="http://www.saout.de/misc/dm-crypt/">dm-crypt</a> page:
</p>
<blockquote><p class="noindent">The defaults [for cryptsetup] are aes with a 256 bit key, <span style="background:#ffff80">hashed using ripemd160</span>. [...]</p>
<p class="noindent"><big><b>Migration from cryptoloop and compatibility</b></big></p>
<p class="noindent">[...]</p>
<p class="noindent">You'll need to figure out how your passphrase was turned into a key to use for losetup. [...]</p>
</blockquote>
<p>That last one turned out to be very sound advice. My <code>losetup</code> man page says in the section about the <code>-e</code> (encryption) option:</p>
<blockquote><div><b>AES128 AES</b></div>
<div style="margin-left:4em">Use 128 bit AES encryption. Passphrase is <span style="background:#ffff80">hashed with SHA-256</span> by default.</div>
</blockquote>
<p>Aaaaaah... so that was why the decryption wasn't correctly giving a mountable volume. Ok, there's a <code>-h</code> option to select the hash, and a <code>-s</code> option to select the cipher's block size which I already was using. Putting all together:
</p>
<pre style="margin-left:4em">
cryptsetup create -c aes -s 128 -h sha256 mappername devicename
</pre>
<p class="noindent">finally did the trick and I could mount my encrypted device. The whole recipe to substitute <kbd>mount -o loop,encryption=AES file mountpoint</kbd> was:
</p>
<pre style="margin-left:4em">
modprobe dm-mod
losetup -f # outputs /dev/loopX to be used below
losetup /dev/loopX file
cryptsetup create -c aes -s 128 -h sha256 mappername /dev/loopX
mount /dev/mapper/mappername mountpoint
</pre>
pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com0tag:blogger.com,1999:blog-7525171767194694125.post-68528494205646995092013-05-04T20:30:00.000+02:002013-05-04T22:44:26.349+02:00You know you've played too much Elite when...<p class="noindent">... you want to visit Ribilebi just to taste their<del class="del" datetime="2013-05-04T20:40:00Z" title="Update 20:40Z"> famous</del><ins class="ins" datetime="2013-05-04T20:40:00Z" title="Update 20:40Z"> fabulous</ins> goat soup.
</p>pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com0tag:blogger.com,1999:blog-7525171767194694125.post-90858421795502284432013-04-25T13:09:00.000+02:002013-04-25T13:09:01.048+02:00Wii Sports - Tennis skill points system<p>I've been for a while trying to figure out the skill points rating system used by Wii Sports Tennis. I think I have it mostly figured out for the case of always the same rivals. My current problem is how the next rivals' skill level is determined.
</p>
<p>First, a brief intro. The ranking system only applies to playing against the computer (otherwise I guess it would be too easy to cheat by playing against an unresponsive player). It rewards you more the more skilled your rivals are, the better score you get and the lower your ranking is, and penalizes you harder the higher your ranking is, the lower your score is and the worse your rivals are. If you win, your rivals' skill grows (up to a maximum); if you lose, it decreases (down to a minimum). There, I said it was brief.
</p>
<p>The variables that influence the skill points are: current skill, current rival's skill, and score in each game (note "game", not "match", though I haven't thoroughly investigated multiple game matches). How exactly?
</p>
<p>The skill points are a floating point number, of which only its integer part (its floor, i.e. rounded down, not rounded to nearest) is visible. A combination of the game score and the rival's skill gives the limit (the asymptote) for an <a href="http://en.wikipedia.org/wiki/Exponential_decay">exponential decay</a> function that when starting with zero points, has the form <i class="math">a</i>·(1-<i class="math">b<sup>-t</sup></i>) for a certain asymptote <i class="math">a</i> and a certain base <i class="math">b</i> which equals 20/19 in this case, with <i class="math">t</i> being the number of games played so far. That function is "factory-tuned" as follows: first, with a zero skill rival and starting at zero points, depending on the score you get, you earn the following skill points:
</p>
<ul>
<li>Win 40-0: 60 points</li>
<li>Win 40-15: 52.5 points</li>
<li>Win 40-30: 45 points</li>
<li>Win Adv-40: 40 points</li>
<li>Lose 40-Adv: 20 points</li>
<li>Lose 30-40: 15 points</li>
<li>Lose 15-40: 7.5 points</li>
<li>Lose 0-40: 0 points</li>
</ul>
<p>(Note it doesn't matter how you reach the win or lose situation, or how many times you get to deuce during a game; only the final result matters). The asymptote of the exponential function will always be 20 times that value:
</p>
<ul>
<li>Series of 40-0 wins: the asymptote will be 1200 points</li>
<li>Series of 40-15 wins: the asymptote will be 1050 points</li>
<li>Series of 40-30 wins: the asymptote will be 900 points</li>
<li>Series of Adv-40 wins: the asymptote will be 800 points</li>
<li>Series of 40-Adv losses: the asymptote will be 400 points</li>
<li>Series of 30-40 losses: the asymptote will be 300 points</li>
<li>Series of 15-40 losses: the asymptote will be 150 points</li>
<li>Series of 0-40 losses: the asymptote will be 0 points</li>
</ul>
<p>Based on the above, we can now figure out the function and parameters for a zero skill rival. Here it is:
</p>
<p class="noindent math" style="margin-left:4em">S<sub>new</sub> = (<i>a</i> + 19·S<sub>previous</sub>)/20
</p>
<p class="noindent">where <i class="math">a</i> is the asymptote, and <span class="mathrm">S<sub>previous</sub></span> and <span class="mathrm">S<sub>new</sub></span> are the skill points before and after the game, respectively. The 20 (and the 19 which is just one less than that) is the multiplier between the score in the first game and the corresponding asymptote as explained above.
</p>
<p>The effect of the rival's skill seems to be to offset the asymptote. When the rival's skill is maxed out (Elisa and Sarah), the offset is 1200. That gives the famous asymptote of 2400 that nobody can reach (because that's how asymptotes work). For example, if you won all your games by 40-15 against Elisa and Sarah, the asymptote would be 2250 (1200+1050). That also means that when you get a skill of 2250 or more, you always lose points if you don't win 40-0 (ok, ok, technically it's more proper to say that you never earn points).
</p>
<p>(By the way, there's a couple claims of reaching more than 2399 points but I don't believe them. One claims to get 2403 but it's obviously fake; the other claims 2400 exactly.)</p>
<p>The only big remaining problem I have is how to determine what's the skill of the next rival you will confront. Using a spreadsheet, I've found that the asymptote offset seems to resemble a <a href="http://en.wikipedia.org/wiki/Logistic_function">sigmoid function</a> as the player keeps winning, which is funny because the <a href="http://en.wikipedia.org/wiki/Elo_rating_system">Elo rating system</a> uses it, though not in the same way, I think (I know very little about it so I can't judge). And not only the offset needs to be determined, but it would be nice to find out the actual ranking too. There seems to be some kind of higher level rounding in the rivals' visible skills: you don't face rivals with a skill of 1317, for example; they are rounded to 1300.
</p>
<p>Other problems I have not investigated are how a second player affects your rating, and how exactly your score varies in case of a multi-set match. If you win all games of a match with the same score, the effect on the skill points seems to be like playing these games in one-game matches one by one, except that the rivals' skill doesn't change.
</p>
<h4>Game Logs</h4>
<p>If you want to help, here are some logs of scores I got from the game and the values I got from the spreadsheet. If there is a testable hypothesis that you think could be resolved with an experiment to help determine the rivals formula, but don't feel like launching Tennis just to test, please say so in the comments, and I may take the task of finding out the result. Currently I'm quite lost on how to determine it.
</p>
<p>The first table below determines how many points you get at start. It gave me the first hints on how the scoring system works. Note that even if the points are rounded down, the skill raises to 8 and 53 instead of 7 and 52 for 15-40 and 40-15 respectively. That's because the first rivals skill is not zero, and that difference makes that score give something between 0.5 and 1 more points, that bumps the number. I actually tested that later (see Table 6 below).
</p>
<p>Unlike the rest, this table is restarted using a new guest player at every row. The rest are restarted only for the first row.</p>
<table class="linetable center" cellspacing="0" cellpadding="0"><caption>Table 1: Scores at the beginning
</caption><thead>
<tr><th>Rivals<br />skill</th><th>Skill<br />before</th><th>Score</th><th>Points</th><th>Skill<br />after</th><th>Next<br />rivals<br />skill</th></tr>
</thead><tbody>
<tr><td>63/49</td><td>0</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+60</td><td>60</td><td>83/65</td></tr>
<tr><td>63/49</td><td>0</td><td style="background:#EFF">40-15</td><td style="background:#EFE">+53</td><td>53</td><td>78/49</td></tr>
<tr><td>63/49</td><td>0</td><td style="background:#EFF">40-30</td><td style="background:#EFE">+45</td><td>45</td><td>73/49</td></tr>
<tr><td>63/49</td><td>0</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+40</td><td>40</td><td>70/49</td></tr>
<tr><td>63/49</td><td>0</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+20</td><td>20</td><td>57/35</td></tr>
<tr><td>63/49</td><td>0</td><td style="background:#FFE">30-40</td><td style="background:#EFE">+15</td><td>15</td><td>54/35</td></tr>
<tr><td>63/49</td><td>0</td><td style="background:#FFE">15-40</td><td style="background:#EFE">+8</td><td>8</td><td>50/35</td></tr>
<tr><td>63/49</td><td>0</td><td style="background:#FFE">0-40</td><td>±0</td><td>0</td><td>45/23</td></tr>
</tbody></table>
<p>Now a series of all 40-0. It's interesting to note that at first, the skill increment decreases, but as the rivals' skill rate grows, the increment rate goes from decreasing to increasing and keeps being that way until dealing with the best rivals, at which moment it suddenly switches to decreasing again.
</p>
<table class="linetable center" cellspacing="0" cellpadding="0"><caption>Table 2: Series of 40-0 wins
</caption><thead>
<tr><th>Rivals<br />skill</th><th>Skill<br />before</th><th>Score</th><th>Points</th><th>Skill<br />after</th><th>Next<br />rivals<br />skill</th></tr>
</thead><tbody>
<tr><td>63/49</td><td>0</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+60</td><td>60</td><td>83/65</td></tr>
<tr><td>83/65</td><td>60</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+58</td><td>118</td><td>120/100</td></tr>
<tr><td>120/100</td><td>118</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+55</td><td>173</td><td>190/160</td></tr>
<tr><td>190/160</td><td>173</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+54</td><td>227</td><td>270/230</td></tr>
<tr><td>270/230</td><td>227</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+53</td><td>280</td><td>380/340</td></tr>
<tr><td>380/340</td><td>280</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+53</td><td>333</td><td>520/490</td></tr>
<tr><td>520/490</td><td>333</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+53</td><td>386</td><td>670/650</td></tr>
<tr><td>670/650</td><td>386</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+55</td><td>441</td><td>850/840</td></tr>
<tr><td>850/840</td><td>441</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+57</td><td>498</td><td>1100/1000</td></tr>
<tr><td>1100/1000</td><td>498</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+61</td><td>559</td><td>1300/1200</td></tr>
<tr><td>1300/1200</td><td>559</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+65</td><td>624</td><td>1500/1400</td></tr>
<tr><td>1500/1400</td><td>624</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+68</td><td>692</td><td>1600/1600</td></tr>
<tr><td>1600/1600</td><td>692</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+71</td><td>763</td><td>1800/1800</td></tr>
<tr><td>1800/1800</td><td>763</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+72</td><td>835</td><td>1900/1800</td></tr>
<tr><td>1900/1800</td><td>835</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+73</td><td>908</td><td>1900/1900</td></tr>
<tr><td>1900/1900</td><td>908</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+73</td><td>981</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>981</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+71</td><td>1052</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1052</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+67</td><td>1119</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1119</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+64</td><td>1183</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1183</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+61</td><td>1244</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1244</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+58</td><td>1302</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1302</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+55</td><td>1357</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1357</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+52</td><td>1409</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1409</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+49</td><td>1458</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1458</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+47</td><td>1505</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1505</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+45</td><td>1550</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1550</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+43</td><td>1593</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1593</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+40</td><td>1633</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1633</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+38</td><td>1671</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1671</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+37</td><td>1708</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1708</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+34</td><td>1742</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1742</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+33</td><td>1775</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1775</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+31</td><td>1806</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1806</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+30</td><td>1836</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1836</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+28</td><td>1864</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1864</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+27</td><td>1891</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1891</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+25</td><td>1916</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1916</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+25</td><td>1941</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1941</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+22</td><td>1963</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1963</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+22</td><td>1985</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1985</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+21</td><td>2006</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>2006</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+20</td><td>2026</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>2026</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+18</td><td>2044</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>2044</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+18</td><td>2062</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>2062</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+17</td><td>2079</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>2079</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+16</td><td>2095</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>2095</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+15</td><td>2110</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>2110</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+15</td><td>2125</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>2125</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+13</td><td>2138</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>2138</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+13</td><td>2151</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>2151</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+13</td><td>2164</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>2164</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+12</td><td>2176</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>2176</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+11</td><td>2187</td><td>2000/1900</td></tr>
</tbody></table>
<p>A series of 0-40 followed by a series of 40-0. Observe how our skill increases to 1 even if we were losing from the very beginning. That's because the rivals' skill was not zero, thus the asymptote goes a bit over 1. But as games progress and rivals are weaker, that point drops again. Note also how the second rival's skill <em>increases</em> at some point. I believe that what we see is the absolute value of the second rival's computed skill, which goes actually down to -4 (from 4/0 to 4/-3 to 2/-4 to 1/-4 to 0/-4).
</p>
<table class="linetable center" cellspacing="0" cellpadding="0"><caption>Table 3: Series of 0-40 losses followed by a series of 40-0 wins
</caption><thead>
<tr><th>Rivals<br />skill</th><th>Skill<br />before</th><th>Score</th><th>Points</th><th>Skill<br />after</th><th>Next<br />rivals<br />skill</th></tr>
</thead><tbody>
<tr><td>63/49</td><td>0</td><td style="background:#FFE">0-40</td><td>±0</td><td>0</td><td>45/23</td></tr>
<tr><td>45/23</td><td>0</td><td style="background:#FFE">0-40</td><td>±0</td><td>0</td><td>32/12</td></tr>
<tr><td>32/12</td><td>0</td><td style="background:#FFE">0-40</td><td style="background:#EFE">+1</td><td>1</td><td>23/12</td></tr>
<tr><td>23/12</td><td>1</td><td style="background:#FFE">0-40</td><td>±0</td><td>1</td><td>17/4</td></tr>
<tr><td>17/4</td><td>1</td><td style="background:#FFE">0-40</td><td>±0</td><td>1</td><td>12/0</td></tr>
<tr><td>12/0</td><td>1</td><td style="background:#FFE">0-40</td><td>±0</td><td>1</td><td>8/0</td></tr>
<tr><td>8/0</td><td>1</td><td style="background:#FFE">0-40</td><td>±0</td><td>1</td><td>6/0</td></tr>
<tr><td>6/0</td><td>1</td><td style="background:#FFE">0-40</td><td>±0</td><td>1</td><td>4/0</td></tr>
<tr><td>4/0</td><td>1</td><td style="background:#FFE">0-40</td><td>±0</td><td>1</td><td>4/3</td></tr>
<tr><td>4/3</td><td>1</td><td style="background:#FFE">0-40</td><td>±0</td><td>1</td><td>2/4</td></tr>
<tr><td>2/4</td><td>1</td><td style="background:#FFE">0-40</td><td>±0</td><td>1</td><td>2/4</td></tr>
<tr><td>2/4</td><td>1</td><td style="background:#FFE">0-40</td><td>±0</td><td>1</td><td>1/4</td></tr>
<tr><td>1/4</td><td>1</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-1</td><td>0</td><td>1/4</td></tr>
<tr><td>1/4</td><td>0</td><td style="background:#FFE">0-40</td><td>±0</td><td>0</td><td>1/4</td></tr>
<tr><td>1/4</td><td>0</td><td style="background:#FFE">0-40</td><td>±0</td><td>0</td><td>0/4</td></tr>
<tr><td>0/4</td><td>0</td><td style="background:#FFE">0-40</td><td>±0</td><td>0</td><td>0/4</td></tr>
<tr><td>0/4</td><td>0</td><td style="background:#FFE">0-40</td><td>±0</td><td>0</td><td>0/4</td></tr>
<tr><td>0/4</td><td>0</td><td style="background:#FFE">0-40</td><td>±0</td><td>0</td><td>0/4</td></tr>
<tr><td>0/4</td><td>0</td><td style="background:#FFE">0-40</td><td>±0</td><td>0</td><td>0/4</td></tr>
<tr><td>0/4</td><td>0</td><td style="background:#FFE">0-40</td><td>±0</td><td>0</td><td>0/4</td></tr>
<tr><td>0/4</td><td>0</td><td style="background:#FFE">0-40</td><td>±0</td><td>0</td><td>0/4</td></tr>
<tr><td>0/4</td><td>0</td><td style="background:#FFE">0-40</td><td>±0</td><td>0</td><td>0/4</td></tr>
<tr><td>0/4</td><td>0</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+60</td><td>60</td><td>6/0</td></tr>
<tr><td>6/0</td><td>60</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+57</td><td>117</td><td>27/12</td></tr>
<tr><td>27/12</td><td>117</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+54</td><td>171</td><td>68/49</td></tr>
<tr><td>68/49</td><td>171</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+52</td><td>223</td><td>130/100</td></tr>
<tr><td>130/100</td><td>223</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+51</td><td>274</td><td>220/180</td></tr>
<tr><td>220/180</td><td>274</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+49</td><td>323</td><td>340/310</td></tr>
<tr><td>340/310</td><td>323</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+50</td><td>373</td><td>480/460</td></tr>
<tr><td>480/460</td><td>373</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+50</td><td>423</td><td>640/620</td></tr>
<tr><td>640/620</td><td>423</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+52</td><td>475</td><td>820/800</td></tr>
<tr><td>820/800</td><td>475</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+55</td><td>530</td><td>1000/990</td></tr>
<tr><td>1000/990</td><td>530</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+58</td><td>588</td><td>1300/1200</td></tr>
<tr><td>1300/1200</td><td>588</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+63</td><td>651</td><td>1500/1400</td></tr>
<tr><td>1500/1400</td><td>651</td><td style="background:#EFF">40-0</td><td style="background:#EFE">+66</td><td>717</td><td>1600/1600</td></tr>
</tbody></table>
<p>Results for a short series of three-game matches won 40-0, 40-0:
</p>
<table class="linetable center" cellspacing="0" cellpadding="0"><caption>Table 4: Series of 40-0, 40-0 wins in three-game matches
</caption><thead>
<tr><th>Rivals<br />skill</th><th>Skill<br />before</th><th>Score<br />1st<br />game</th><th>Score<br />2nd<br />game</th><th>Score<br />3rd<br />game</th><th>Points</th><th>Skill<br />after</th><th>Next<br />rivals<br />skill</th></tr>
</thead><tbody>
<tr><td>63/49</td><td>0</td><td style="background:#EFF">40-0</td><td style="background:#EFF">40-0</td><td>-</td><td style="background:#EFE">+118</td><td>118</td><td>120/100</td></tr>
<tr><td>120/100</td><td>118</td><td style="background:#EFF">40-0</td><td style="background:#EFF">40-0</td><td>-</td><td style="background:#EFE">+108</td><td>226</td><td>270/230</td></tr>
<tr><td>270/230</td><td>226</td><td style="background:#EFF">40-0</td><td style="background:#EFF">40-0</td><td>-</td><td style="background:#EFE">+103</td><td>329</td><td>520/490</td></tr>
</tbody></table>
<p>Same for a short series of five-game matches won 40-0, 40-0, 40-0:
</p>
<table class="linetable center" cellspacing="0" cellpadding="0"><caption>Table 5: Series of 40-0, 40-0, 40-0 wins in five-game matches
</caption><thead>
<tr><th>Rivals<br />skill</th><th>Skill<br />before</th><th>Score<br />1st<br />game</th><th>Score<br />2nd<br />game</th><th>Score<br />3rd<br />game</th><th>Score<br />4th<br />game</th><th>Score<br />5th<br />game</th><th>Points</th><th>Skill<br />after</th><th>Next<br />rivals<br />skill</th></tr>
</thead><tbody>
<tr><td>63/49</td><td>0</td><td style="background:#EFF">40-0</td><td style="background:#EFF">40-0</td><td style="background:#EFF">40-0</td><td>-</td><td>-</td><td style="background:#EFE">+172</td><td>172</td><td>190/160</td></tr>
<tr><td>190/160</td><td>172</td><td style="background:#EFF">40-0</td><td style="background:#EFF">40-0</td><td style="background:#EFF">40-0</td><td>-</td><td>-</td><td style="background:#EFE">+154</td><td>326</td><td>540/440</td></tr>
<tr><td>540/440</td><td>326</td><td style="background:#EFF">40-0</td><td style="background:#EFF">40-0</td><td style="background:#EFF">40-0</td><td>-</td><td>-</td><td style="background:#EFE">+173</td><td>479</td><td>1100/1000</td></tr>
</tbody></table>
<p>In the next series, I first went as close to 0.0 points as my patience could bear, with the rivals as low as possible. Then I scored a 15-40 to confirm my hypothesis that the points are rounded down, not to nearest, by checking whether it went up to 7 or to 8. As I expected, it went up to only 7, proving my hypothesis. Then I did a few more experiments to get a bit more understanding on how the rates of the rivals worked, specifically if they never increased if I lost, even if my skill increased. That seems to be the case, but I need more experimenting in this field.
</p>
<table class="linetable center" cellspacing="0" cellpadding="0"><caption>Table 6: Series of 0-40 losses followed by a 15-40 loss, and more checks on the effects of various scores over the rivals' skill.
</caption><thead>
<tr><th>Rivals<br />skill</th><th>Skill<br />before</th><th>Score</th><th>Points</th><th>Skill<br />after</th><th>Next<br />rivals<br />skill</th></tr>
</thead><tbody>
<tr><td colspan="6" style="background:#CCE">(First 15 rows as in Table 3)</td></tr>
<tr><td>0/4</td><td>0</td><td style="background:#FFE">0-40</td><td>±0</td><td>0</td><td>0/4</td></tr>
<tr><td colspan="6" style="background:#CCE">(Repeat the previous row 24 more times, total 25 times)</td></tr>
<tr><td>0/4</td><td>0</td><td style="background:#FFE">15-40</td><td style="background:#EFE">+7</td><td>7</td><td>0/4</td></tr>
<tr><td>0/4</td><td>7</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-1</td><td>6</td><td>0/4</td></tr>
<tr><td>0/4</td><td>6</td><td style="background:#FFE">0-40</td><td>±0</td><td>6</td><td>0/4</td></tr>
<tr><td>0/4</td><td>6</td><td style="background:#FFE">0-40</td><td>±0</td><td>6</td><td>0/4</td></tr>
<tr><td>0/4</td><td>6</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-1</td><td>5</td><td>0/4</td></tr>
<tr><td>0/4</td><td>5</td><td style="background:#FFE">0-40</td><td>±0</td><td>5</td><td>0/4</td></tr>
<tr><td>0/4</td><td>5</td><td style="background:#FFE">0-40</td><td>±0</td><td>5</td><td>0/4</td></tr>
<tr><td>0/4</td><td>5</td><td style="background:#FFE">0-40</td><td>±0</td><td>5</td><td>0/4</td></tr>
<tr><td>0/4</td><td>5</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-1</td><td>4</td><td>0/4</td></tr>
<tr><td>0/4</td><td>4</td><td style="background:#FFE">0-40</td><td>±0</td><td>4</td><td>0/4</td></tr>
<tr><td>0/4</td><td>4</td><td style="background:#FFE">0-40</td><td>±0</td><td>4</td><td>0/4</td></tr>
<tr><td>0/4</td><td>4</td><td style="background:#FFE">0-40</td><td>±0</td><td>4</td><td>0/4</td></tr>
<tr><td>0/4</td><td>4</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-1</td><td>3</td><td>0/4</td></tr>
<tr><td>0/4</td><td>3</td><td style="background:#FFE">0-40</td><td>±0</td><td>3</td><td>0/4</td></tr>
<tr><td>0/4</td><td>3</td><td style="background:#FFE">0-40</td><td>±0</td><td>3</td><td>0/4</td></tr>
<tr><td>0/4</td><td>3</td><td style="background:#FFE">0-40</td><td>±0</td><td>3</td><td>0/4</td></tr>
<tr><td>0/4</td><td>3</td><td style="background:#FFE">0-40</td><td>±0</td><td>3</td><td>0/4</td></tr>
<tr><td>0/4</td><td>3</td><td style="background:#FFE">0-40</td><td>±0</td><td>3</td><td>0/4</td></tr>
<tr><td>0/4</td><td>3</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-1</td><td>2</td><td>0/4</td></tr>
<tr><td>0/4</td><td>2</td><td style="background:#FFE">0-40</td><td>±0</td><td>2</td><td>0/4</td></tr>
<tr><td>0/4</td><td>2</td><td style="background:#FFE">0-40</td><td>±0</td><td>2</td><td>0/4</td></tr>
<tr><td>0/4</td><td>2</td><td style="background:#FFE">0-40</td><td>±0</td><td>2</td><td>0/4</td></tr>
<tr><td>0/4</td><td>2</td><td style="background:#FFE">0-40</td><td>±0</td><td>2</td><td>0/4</td></tr>
<tr><td>0/4</td><td>2</td><td style="background:#FFE">0-40</td><td>±0</td><td>2</td><td>0/4</td></tr>
<tr><td>0/4</td><td>2</td><td style="background:#FFE">0-40</td><td>±0</td><td>2</td><td>0/4</td></tr>
<tr><td>0/4</td><td>2</td><td style="background:#FFE">0-40</td><td>±0</td><td>2</td><td>0/4</td></tr>
<tr><td>0/4</td><td>2</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-1</td><td>1</td><td>0/4</td></tr>
<tr><td>0/4</td><td>1</td><td style="background:#FFE">0-40</td><td>±0</td><td>1</td><td>0/4</td></tr>
<tr><td>0/4</td><td>1</td><td style="background:#FFE">0-40</td><td>±0</td><td>1</td><td>0/4</td></tr>
<tr><td>0/4</td><td>1</td><td style="background:#FFE">0-40</td><td>±0</td><td>1</td><td>0/4</td></tr>
<tr><td>0/4</td><td>1</td><td style="background:#FFE">0-40</td><td>±0</td><td>1</td><td>0/4</td></tr>
<tr><td>0/4</td><td>1</td><td style="background:#FFE">0-40</td><td>±0</td><td>1</td><td>0/4</td></tr>
<tr><td>0/4</td><td>1</td><td style="background:#FFE">0-40</td><td>±0</td><td>1</td><td>0/4</td></tr>
<tr><td>0/4</td><td>1</td><td style="background:#FFE">0-40</td><td>±0</td><td>1</td><td>0/4</td></tr>
<tr><td>0/4</td><td>1</td><td style="background:#FFE">0-40</td><td>±0</td><td>1</td><td>0/4</td></tr>
<tr><td>0/4</td><td>1</td><td style="background:#FFE">0-40</td><td>±0</td><td>1</td><td>0/4</td></tr>
<tr><td>0/4</td><td>1</td><td style="background:#FFE">0-40</td><td>±0</td><td>1</td><td>0/4</td></tr>
<tr><td>0/4</td><td>1</td><td style="background:#FFE">0-40</td><td>±0</td><td>1</td><td>0/4</td></tr>
<tr><td>0/4</td><td>1</td><td style="background:#FFE">0-40</td><td>±0</td><td>1</td><td>0/4</td></tr>
<tr><td>0/4</td><td>1</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-1</td><td>0</td><td>0/4</td></tr>
<tr><td>0/4</td><td>0</td><td style="background:#FFE">15-40</td><td style="background:#EFE">+8</td><td>8</td><td>0/4</td></tr>
<tr><td>0/4</td><td>8</td><td style="background:#FFE">0-40</td><td>±0</td><td>8</td><td>0/4</td></tr>
<tr><td>0/4</td><td>8</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-1</td><td>7</td><td>0/4</td></tr>
<tr><td>0/4</td><td>7</td><td style="background:#FFE">15-40</td><td style="background:#EFE">+7</td><td>14</td><td>0/4</td></tr>
<tr><td>0/4</td><td>14</td><td style="background:#FFE">15-40</td><td style="background:#EFE">+7</td><td>21</td><td>0/4</td></tr>
<tr><td>0/4</td><td>21</td><td style="background:#FFE">15-40</td><td style="background:#EFE">+6</td><td>27</td><td>0/4</td></tr>
<tr><td>0/4</td><td>27</td><td style="background:#FFE">15-40</td><td style="background:#EFE">+7</td><td>34</td><td>0/4</td></tr>
<tr><td>0/4</td><td>34</td><td style="background:#FFE">30-40</td><td style="background:#EFE">+13</td><td>47</td><td>0/4</td></tr>
<tr><td>0/4</td><td>47</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+17</td><td>64</td><td>0/4</td></tr>
<tr><td>0/4</td><td>64</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+37</td><td>101</td><td>1/4</td></tr>
</tbody></table>
<p>This time I went close to zero again, then tested several series of scores. That allowed me to test what the asymptotes were for scores from 0-40 to 40-Adv, while keeping the rivals' scores minimized. It's the longest-running experiment. Believe me, it was <em>tedious</em> to do, but the results were worth it. I gave up on checking if the asymptotes were really asymptotes after 50 tries in two cases. I also tried "jumping to the other side" of the asymptote in the case of 400, just to be sure. For some of them, I suspected what the asymptotes were, so I scored the necessary points to get there as fast as possible without crossing the line.
</p>
<table class="linetable center" cellspacing="0" cellpadding="0"><caption>Table 7: Series of 0-40 losses followed by a series of 40-Adv losses, then test the asymptotes for other scores.
</caption><thead>
<tr><th>Rivals<br />skill</th><th>Skill<br />before</th><th>Score</th><th>Points</th><th>Skill<br />after</th><th>Next<br />rivals<br />skill</th></tr>
</thead><tbody>
<tr><td colspan="6" style="background:#CCE">(First 15 rows as in Table 3)</td></tr>
<tr><td>0/4</td><td>0</td><td style="background:#FFE">0-40</td><td>±0</td><td>0</td><td>0/4</td></tr>
<tr><td colspan="6" style="background:#CCE">(Repeat the previous row 29 more times, total 30 times)</td></tr>
<tr><td>0/4</td><td>0</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+20</td><td>20</td><td>0/4</td></tr>
<tr><td>0/4</td><td>20</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+19</td><td>39</td><td>0/4</td></tr>
<tr><td>0/4</td><td>39</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+18</td><td>57</td><td>0/4</td></tr>
<tr><td>0/4</td><td>57</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+17</td><td>74</td><td>0/4</td></tr>
<tr><td>0/4</td><td>74</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+16</td><td>90</td><td>0/4</td></tr>
<tr><td>0/4</td><td>90</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+16</td><td>106</td><td>0/4</td></tr>
<tr><td>0/4</td><td>106</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+14</td><td>120</td><td>0/4</td></tr>
<tr><td>0/4</td><td>120</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+14</td><td>134</td><td>0/4</td></tr>
<tr><td>0/4</td><td>134</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+14</td><td>148</td><td>0/4</td></tr>
<tr><td>0/4</td><td>148</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+12</td><td>160</td><td>0/4</td></tr>
<tr><td>0/4</td><td>160</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+12</td><td>172</td><td>0/4</td></tr>
<tr><td>0/4</td><td>172</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+11</td><td>183</td><td>0/4</td></tr>
<tr><td>0/4</td><td>183</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+11</td><td>194</td><td>0/4</td></tr>
<tr><td>0/4</td><td>194</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+11</td><td>205</td><td>0/4</td></tr>
<tr><td>0/4</td><td>205</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+9</td><td>214</td><td>0/4</td></tr>
<tr><td>0/4</td><td>214</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+10</td><td>224</td><td>0/4</td></tr>
<tr><td>0/4</td><td>224</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+8</td><td>232</td><td>0/4</td></tr>
<tr><td>0/4</td><td>232</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+9</td><td>241</td><td>0/4</td></tr>
<tr><td>0/4</td><td>241</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+8</td><td>249</td><td>0/4</td></tr>
<tr><td>0/4</td><td>249</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+7</td><td>256</td><td>0/4</td></tr>
<tr><td>0/4</td><td>256</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+7</td><td>263</td><td>0/4</td></tr>
<tr><td>0/4</td><td>263</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+7</td><td>270</td><td>0/4</td></tr>
<tr><td>0/4</td><td>270</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+7</td><td>277</td><td>0/4</td></tr>
<tr><td>0/4</td><td>277</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+6</td><td>283</td><td>0/4</td></tr>
<tr><td>0/4</td><td>283</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+6</td><td>289</td><td>0/4</td></tr>
<tr><td>0/4</td><td>289</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+5</td><td>294</td><td>0/4</td></tr>
<tr><td>0/4</td><td>294</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+5</td><td>299</td><td>0/4</td></tr>
<tr><td>0/4</td><td>299</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+5</td><td>304</td><td>0/4</td></tr>
<tr><td>0/4</td><td>304</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+5</td><td>309</td><td>0/4</td></tr>
<tr><td>0/4</td><td>309</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+5</td><td>314</td><td>0/4</td></tr>
<tr><td>0/4</td><td>314</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+4</td><td>318</td><td>0/4</td></tr>
<tr><td>0/4</td><td>318</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+4</td><td>322</td><td>0/4</td></tr>
<tr><td>0/4</td><td>322</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+4</td><td>326</td><td>0/4</td></tr>
<tr><td>0/4</td><td>326</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+4</td><td>330</td><td>0/4</td></tr>
<tr><td>0/4</td><td>330</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+3</td><td>333</td><td>0/4</td></tr>
<tr><td>0/4</td><td>333</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+3</td><td>336</td><td>0/4</td></tr>
<tr><td>0/4</td><td>336</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+4</td><td>340</td><td>0/4</td></tr>
<tr><td>0/4</td><td>340</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+3</td><td>343</td><td>0/4</td></tr>
<tr><td>0/4</td><td>343</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+2</td><td>345</td><td>0/4</td></tr>
<tr><td>0/4</td><td>348</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+3</td><td>351</td><td>0/4</td></tr>
<tr><td>0/4</td><td>351</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+2</td><td>353</td><td>0/4</td></tr>
<tr><td>0/4</td><td>353</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+2</td><td>355</td><td>0/4</td></tr>
<tr><td>0/4</td><td>355</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+3</td><td>358</td><td>0/4</td></tr>
<tr><td>0/4</td><td>358</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+2</td><td>360</td><td>0/4</td></tr>
<tr><td>0/4</td><td>360</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+2</td><td>362</td><td>0/4</td></tr>
<tr><td>0/4</td><td>362</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+2</td><td>364</td><td>0/4</td></tr>
<tr><td>0/4</td><td>364</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>365</td><td>0/4</td></tr>
<tr><td>0/4</td><td>365</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+2</td><td>367</td><td>0/4</td></tr>
<tr><td>0/4</td><td>367</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+2</td><td>369</td><td>0/4</td></tr>
<tr><td>0/4</td><td>369</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>370</td><td>0/4</td></tr>
<tr><td>0/4</td><td>370</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+2</td><td>372</td><td>0/4</td></tr>
<tr><td>0/4</td><td>372</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>373</td><td>0/4</td></tr>
<tr><td>0/4</td><td>373</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>374</td><td>0/4</td></tr>
<tr><td>0/4</td><td>374</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+2</td><td>376</td><td>0/4</td></tr>
<tr><td>0/4</td><td>376</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>377</td><td>0/4</td></tr>
<tr><td>0/4</td><td>377</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>378</td><td>0/4</td></tr>
<tr><td>0/4</td><td>378</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>379</td><td>0/4</td></tr>
<tr><td>0/4</td><td>379</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>380</td><td>0/4</td></tr>
<tr><td>0/4</td><td>380</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>381</td><td>0/4</td></tr>
<tr><td>0/4</td><td>381</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>382</td><td>0/4</td></tr>
<tr><td>0/4</td><td>382</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>383</td><td>0/4</td></tr>
<tr><td>0/4</td><td>383</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>384</td><td>0/4</td></tr>
<tr><td>0/4</td><td>384</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>384</td><td>0/4</td></tr>
<tr><td>0/4</td><td>384</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>385</td><td>0/4</td></tr>
<tr><td>0/4</td><td>385</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>386</td><td>0/4</td></tr>
<tr><td>0/4</td><td>386</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>387</td><td>0/4</td></tr>
<tr><td>0/4</td><td>387</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>387</td><td>0/4</td></tr>
<tr><td>0/4</td><td>387</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>388</td><td>0/4</td></tr>
<tr><td>0/4</td><td>388</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>388</td><td>0/4</td></tr>
<tr><td>0/4</td><td>388</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>389</td><td>0/4</td></tr>
<tr><td>0/4</td><td>389</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>390</td><td>0/4</td></tr>
<tr><td>0/4</td><td>390</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>390</td><td>0/4</td></tr>
<tr><td>0/4</td><td>390</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>391</td><td>0/4</td></tr>
<tr><td>0/4</td><td>391</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>391</td><td>0/4</td></tr>
<tr><td>0/4</td><td>391</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>391</td><td>0/4</td></tr>
<tr><td>0/4</td><td>391</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>392</td><td>0/4</td></tr>
<tr><td>0/4</td><td>392</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>392</td><td>0/4</td></tr>
<tr><td>0/4</td><td>392</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>393</td><td>0/4</td></tr>
<tr><td>0/4</td><td>393</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>393</td><td>0/4</td></tr>
<tr><td>0/4</td><td>393</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>393</td><td>0/4</td></tr>
<tr><td>0/4</td><td>393</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>394</td><td>0/4</td></tr>
<tr><td>0/4</td><td>394</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>394</td><td>0/4</td></tr>
<tr><td>0/4</td><td>394</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>394</td><td>0/4</td></tr>
<tr><td>0/4</td><td>394</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>394</td><td>0/4</td></tr>
<tr><td>0/4</td><td>394</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>395</td><td>0/4</td></tr>
<tr><td>0/4</td><td>395</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>395</td><td>0/4</td></tr>
<tr><td>0/4</td><td>395</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>395</td><td>0/4</td></tr>
<tr><td>0/4</td><td>395</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>395</td><td>0/4</td></tr>
<tr><td>0/4</td><td>395</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>396</td><td>0/4</td></tr>
<tr><td>0/4</td><td>396</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>396</td><td>0/4</td></tr>
<tr><td>0/4</td><td>396</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>396</td><td>0/4</td></tr>
<tr><td>0/4</td><td>396</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>396</td><td>0/4</td></tr>
<tr><td>0/4</td><td>396</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>396</td><td>0/4</td></tr>
<tr><td>0/4</td><td>396</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>396</td><td>0/4</td></tr>
<tr><td>0/4</td><td>396</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>397</td><td>0/4</td></tr>
<tr><td>0/4</td><td>397</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>397</td><td>0/4</td></tr>
<tr><td>0/4</td><td>397</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>397</td><td>0/4</td></tr>
<tr><td>0/4</td><td>397</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>397</td><td>0/4</td></tr>
<tr><td>0/4</td><td>397</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>397</td><td>0/4</td></tr>
<tr><td>0/4</td><td>397</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>397</td><td>0/4</td></tr>
<tr><td>0/4</td><td>397</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>397</td><td>0/4</td></tr>
<tr><td>0/4</td><td>397</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>397</td><td>0/4</td></tr>
<tr><td>0/4</td><td>397</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>398</td><td>0/4</td></tr>
<tr><td>0/4</td><td>398</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>398</td><td>0/4</td></tr>
<tr><td>0/4</td><td>398</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>398</td><td>0/4</td></tr>
<tr><td>0/4</td><td>398</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>398</td><td>0/4</td></tr>
<tr><td>0/4</td><td>398</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>398</td><td>0/4</td></tr>
<tr><td>0/4</td><td>398</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>398</td><td>0/4</td></tr>
<tr><td>0/4</td><td>398</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>398</td><td>0/4</td></tr>
<tr><td>0/4</td><td>398</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>398</td><td>0/4</td></tr>
<tr><td>0/4</td><td>398</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>398</td><td>0/4</td></tr>
<tr><td>0/4</td><td>398</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>398</td><td>0/4</td></tr>
<tr><td>0/4</td><td>398</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>398</td><td>0/4</td></tr>
<tr><td>0/4</td><td>398</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>398</td><td>0/4</td></tr>
<tr><td>0/4</td><td>398</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>398</td><td>0/4</td></tr>
<tr><td>0/4</td><td>398</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+1</td><td>399</td><td>0/4</td></tr>
<tr><td>0/4</td><td>399</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>399</td><td>0/4</td></tr>
<tr><td colspan="6" style="background:#CCE">(Repeat the previous row 49 more times, total 50 times)</td></tr>
<tr><td>0/4</td><td>399</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+20</td><td>419</td><td>1/4</td></tr>
<tr><td>1/4</td><td>419</td><td style="background:#FFE">40-Adv</td><td style="background:#FEE">-1</td><td>418</td><td>1/4</td></tr>
<tr><td>1/4</td><td>418</td><td style="background:#FFE">40-Adv</td><td style="background:#FEE">-1</td><td>417</td><td>1/4</td></tr>
<tr><td>1/4</td><td>417</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>417</td><td>0/4</td></tr>
<tr><td>0/4</td><td>417</td><td style="background:#FFE">40-Adv</td><td style="background:#FEE">-1</td><td>416</td><td>0/4</td></tr>
<tr><td>0/4</td><td>416</td><td style="background:#FFE">40-Adv</td><td style="background:#FEE">-1</td><td>415</td><td>0/4</td></tr>
<tr><td>0/4</td><td>415</td><td style="background:#FFE">40-Adv</td><td style="background:#FEE">-1</td><td>414</td><td>0/4</td></tr>
<tr><td>0/4</td><td>414</td><td style="background:#FFE">40-Adv</td><td style="background:#FEE">-1</td><td>413</td><td>0/4</td></tr>
<tr><td>0/4</td><td>413</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>413</td><td>0/4</td></tr>
<tr><td>0/4</td><td>413</td><td style="background:#FFE">40-Adv</td><td style="background:#FEE">-1</td><td>412</td><td>0/4</td></tr>
<tr><td>0/4</td><td>412</td><td style="background:#FFE">40-Adv</td><td style="background:#FEE">-1</td><td>411</td><td>0/4</td></tr>
<tr><td>0/4</td><td>411</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>411</td><td>0/4</td></tr>
<tr><td>0/4</td><td>411</td><td style="background:#FFE">40-Adv</td><td style="background:#FEE">-1</td><td>410</td><td>0/4</td></tr>
<tr><td>0/4</td><td>410</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>410</td><td>0/4</td></tr>
<tr><td>0/4</td><td>410</td><td style="background:#FFE">40-Adv</td><td style="background:#FEE">-1</td><td>409</td><td>0/4</td></tr>
<tr><td>0/4</td><td>409</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>409</td><td>0/4</td></tr>
<tr><td>0/4</td><td>409</td><td style="background:#FFE">40-Adv</td><td style="background:#FEE">-1</td><td>408</td><td>0/4</td></tr>
<tr><td>0/4</td><td>408</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>408</td><td>0/4</td></tr>
<tr><td>0/4</td><td>408</td><td style="background:#FFE">40-Adv</td><td style="background:#FEE">-1</td><td>407</td><td>0/4</td></tr>
<tr><td>0/4</td><td>407</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>407</td><td>0/4</td></tr>
<tr><td>0/4</td><td>407</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>407</td><td>0/4</td></tr>
<tr><td>0/4</td><td>407</td><td style="background:#FFE">40-Adv</td><td style="background:#FEE">-1</td><td>406</td><td>0/4</td></tr>
<tr><td>0/4</td><td>406</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>406</td><td>0/4</td></tr>
<tr><td>0/4</td><td>406</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>406</td><td>0/4</td></tr>
<tr><td>0/4</td><td>406</td><td style="background:#FFE">40-Adv</td><td style="background:#FEE">-1</td><td>405</td><td>0/4</td></tr>
<tr><td>0/4</td><td>405</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>405</td><td>0/4</td></tr>
<tr><td>0/4</td><td>405</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>405</td><td>0/4</td></tr>
<tr><td>0/4</td><td>405</td><td style="background:#FFE">40-Adv</td><td style="background:#FEE">-1</td><td>404</td><td>0/4</td></tr>
<tr><td>0/4</td><td>404</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>404</td><td>0/4</td></tr>
<tr><td>0/4</td><td>404</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>404</td><td>0/4</td></tr>
<tr><td>0/4</td><td>404</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>404</td><td>0/4</td></tr>
<tr><td>0/4</td><td>404</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>404</td><td>0/4</td></tr>
<tr><td>0/4</td><td>404</td><td style="background:#FFE">40-Adv</td><td style="background:#FEE">-1</td><td>403</td><td>0/4</td></tr>
<tr><td>0/4</td><td>403</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>403</td><td>0/4</td></tr>
<tr><td>0/4</td><td>403</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>403</td><td>0/4</td></tr>
<tr><td>0/4</td><td>403</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>403</td><td>0/4</td></tr>
<tr><td>0/4</td><td>403</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>403</td><td>0/4</td></tr>
<tr><td>0/4</td><td>403</td><td style="background:#FFE">40-Adv</td><td style="background:#FEE">-1</td><td>402</td><td>0/4</td></tr>
<tr><td>0/4</td><td>402</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>402</td><td>0/4</td></tr>
<tr><td>0/4</td><td>402</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>402</td><td>0/4</td></tr>
<tr><td>0/4</td><td>402</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>402</td><td>0/4</td></tr>
<tr><td>0/4</td><td>402</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>402</td><td>0/4</td></tr>
<tr><td>0/4</td><td>402</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>402</td><td>0/4</td></tr>
<tr><td>0/4</td><td>402</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>402</td><td>0/4</td></tr>
<tr><td>0/4</td><td>402</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>402</td><td>0/4</td></tr>
<tr><td>0/4</td><td>402</td><td style="background:#FFE">40-Adv</td><td style="background:#FEE">-1</td><td>401</td><td>0/4</td></tr>
<tr><td>0/4</td><td>401</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>401</td><td>0/4</td></tr>
<tr><td>0/4</td><td>401</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>401</td><td>0/4</td></tr>
<tr><td>0/4</td><td>401</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>401</td><td>0/4</td></tr>
<tr><td>0/4</td><td>401</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>401</td><td>0/4</td></tr>
<tr><td>0/4</td><td>401</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>401</td><td>0/4</td></tr>
<tr><td>0/4</td><td>401</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>401</td><td>0/4</td></tr>
<tr><td>0/4</td><td>401</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>401</td><td>0/4</td></tr>
<tr><td>0/4</td><td>401</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>401</td><td>0/4</td></tr>
<tr><td>0/4</td><td>401</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>401</td><td>0/4</td></tr>
<tr><td>0/4</td><td>401</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>401</td><td>0/4</td></tr>
<tr><td>0/4</td><td>401</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>401</td><td>0/4</td></tr>
<tr><td>0/4</td><td>401</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>401</td><td>0/4</td></tr>
<tr><td>0/4</td><td>401</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>401</td><td>0/4</td></tr>
<tr><td>0/4</td><td>401</td><td style="background:#FFE">40-Adv</td><td style="background:#FEE">-1</td><td>400</td><td>0/4</td></tr>
<tr><td>0/4</td><td>400</td><td style="background:#FFE">40-Adv</td><td>±0</td><td>400</td><td>0/4</td></tr>
<tr><td colspan="6" style="background:#CCE">(Repeat the previous row 49 more times, total 50 times)</td></tr>
<tr><td>0/4</td><td>400</td><td style="background:#FFE">30-40</td><td style="background:#FEE">-5</td><td>395</td><td>0/4</td></tr>
<tr><td>0/4</td><td>395</td><td style="background:#FFE">30-40</td><td style="background:#FEE">-5</td><td>390</td><td>0/4</td></tr>
<tr><td>0/4</td><td>390</td><td style="background:#FFE">30-40</td><td style="background:#FEE">-5</td><td>385</td><td>0/4</td></tr>
<tr><td>0/4</td><td>385</td><td style="background:#FFE">30-40</td><td style="background:#FEE">-4</td><td>381</td><td>0/4</td></tr>
<tr><td>0/4</td><td>381</td><td style="background:#FFE">30-40</td><td style="background:#FEE">-4</td><td>377</td><td>0/4</td></tr>
<tr><td>0/4</td><td>377</td><td style="background:#FFE">30-40</td><td style="background:#FEE">-4</td><td>373</td><td>0/4</td></tr>
<tr><td>0/4</td><td>373</td><td style="background:#FFE">30-40</td><td style="background:#FEE">-4</td><td>369</td><td>0/4</td></tr>
<tr><td>0/4</td><td>369</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-18</td><td>351</td><td>0/4</td></tr>
<tr><td>0/4</td><td>351</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-18</td><td>333</td><td>0/4</td></tr>
<tr><td>0/4</td><td>333</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-16</td><td>317</td><td>0/4</td></tr>
<tr><td>0/4</td><td>317</td><td style="background:#FFE">15-40</td><td style="background:#FEE">-9</td><td>308</td><td>0/4</td></tr>
<tr><td>0/4</td><td>308</td><td style="background:#FFE">30-40</td><td>±0</td><td>308</td><td>0/4</td></tr>
<tr><td>0/4</td><td>308</td><td style="background:#FFE">30-40</td><td style="background:#FEE">-1</td><td>307</td><td>0/4</td></tr>
<tr><td>0/4</td><td>307</td><td style="background:#FFE">30-40</td><td>±0</td><td>307</td><td>0/4</td></tr>
<tr><td>0/4</td><td>307</td><td style="background:#FFE">30-40</td><td>±0</td><td>307</td><td>0/4</td></tr>
<tr><td>0/4</td><td>307</td><td style="background:#FFE">30-40</td><td style="background:#FEE">-1</td><td>306</td><td>0/4</td></tr>
<tr><td>0/4</td><td>306</td><td style="background:#FFE">30-40</td><td>±0</td><td>306</td><td>0/4</td></tr>
<tr><td>0/4</td><td>306</td><td style="background:#FFE">30-40</td><td>±0</td><td>306</td><td>0/4</td></tr>
<tr><td>0/4</td><td>306</td><td style="background:#FFE">30-40</td><td style="background:#FEE">-1</td><td>305</td><td>0/4</td></tr>
<tr><td>0/4</td><td>305</td><td style="background:#FFE">30-40</td><td>±0</td><td>305</td><td>0/4</td></tr>
<tr><td>0/4</td><td>305</td><td style="background:#FFE">30-40</td><td>±0</td><td>305</td><td>0/4</td></tr>
<tr><td>0/4</td><td>305</td><td style="background:#FFE">30-40</td><td style="background:#FEE">-1</td><td>304</td><td>0/4</td></tr>
<tr><td>0/4</td><td>304</td><td style="background:#FFE">30-40</td><td>±0</td><td>304</td><td>0/4</td></tr>
<tr><td>0/4</td><td>304</td><td style="background:#FFE">30-40</td><td>±0</td><td>304</td><td>0/4</td></tr>
<tr><td>0/4</td><td>304</td><td style="background:#FFE">30-40</td><td>±0</td><td>304</td><td>0/4</td></tr>
<tr><td>0/4</td><td>304</td><td style="background:#FFE">30-40</td><td>±0</td><td>304</td><td>0/4</td></tr>
<tr><td>0/4</td><td>304</td><td style="background:#FFE">30-40</td><td style="background:#FEE">-1</td><td>303</td><td>0/4</td></tr>
<tr><td>0/4</td><td>303</td><td style="background:#FFE">30-40</td><td>±0</td><td>303</td><td>0/4</td></tr>
<tr><td>0/4</td><td>303</td><td style="background:#FFE">30-40</td><td>±0</td><td>303</td><td>0/4</td></tr>
<tr><td>0/4</td><td>303</td><td style="background:#FFE">30-40</td><td>±0</td><td>303</td><td>0/4</td></tr>
<tr><td>0/4</td><td>303</td><td style="background:#FFE">30-40</td><td>±0</td><td>303</td><td>0/4</td></tr>
<tr><td>0/4</td><td>303</td><td style="background:#FFE">30-40</td><td style="background:#FEE">-1</td><td>302</td><td>0/4</td></tr>
<tr><td>0/4</td><td>302</td><td style="background:#FFE">30-40</td><td>±0</td><td>302</td><td>0/4</td></tr>
<tr><td>0/4</td><td>302</td><td style="background:#FFE">30-40</td><td>±0</td><td>302</td><td>0/4</td></tr>
<tr><td>0/4</td><td>302</td><td style="background:#FFE">30-40</td><td>±0</td><td>302</td><td>0/4</td></tr>
<tr><td>0/4</td><td>302</td><td style="background:#FFE">30-40</td><td>±0</td><td>302</td><td>0/4</td></tr>
<tr><td>0/4</td><td>302</td><td style="background:#FFE">30-40</td><td>±0</td><td>302</td><td>0/4</td></tr>
<tr><td>0/4</td><td>302</td><td style="background:#FFE">30-40</td><td>±0</td><td>302</td><td>0/4</td></tr>
<tr><td>0/4</td><td>302</td><td style="background:#FFE">30-40</td><td>±0</td><td>302</td><td>0/4</td></tr>
<tr><td>0/4</td><td>302</td><td style="background:#FFE">30-40</td><td style="background:#FEE">-1</td><td>301</td><td>0/4</td></tr>
<tr><td>0/4</td><td>301</td><td style="background:#FFE">30-40</td><td>±0</td><td>301</td><td>0/4</td></tr>
<tr><td>0/4</td><td>301</td><td style="background:#FFE">30-40</td><td>±0</td><td>301</td><td>0/4</td></tr>
<tr><td>0/4</td><td>301</td><td style="background:#FFE">30-40</td><td>±0</td><td>301</td><td>0/4</td></tr>
<tr><td>0/4</td><td>301</td><td style="background:#FFE">30-40</td><td>±0</td><td>301</td><td>0/4</td></tr>
<tr><td>0/4</td><td>301</td><td style="background:#FFE">30-40</td><td>±0</td><td>301</td><td>0/4</td></tr>
<tr><td>0/4</td><td>301</td><td style="background:#FFE">30-40</td><td>±0</td><td>301</td><td>0/4</td></tr>
<tr><td>0/4</td><td>301</td><td style="background:#FFE">30-40</td><td>±0</td><td>301</td><td>0/4</td></tr>
<tr><td>0/4</td><td>301</td><td style="background:#FFE">30-40</td><td>±0</td><td>301</td><td>0/4</td></tr>
<tr><td>0/4</td><td>301</td><td style="background:#FFE">30-40</td><td>±0</td><td>301</td><td>0/4</td></tr>
<tr><td>0/4</td><td>301</td><td style="background:#FFE">30-40</td><td>±0</td><td>301</td><td>0/4</td></tr>
<tr><td>0/4</td><td>301</td><td style="background:#FFE">30-40</td><td>±0</td><td>301</td><td>0/4</td></tr>
<tr><td>0/4</td><td>301</td><td style="background:#FFE">30-40</td><td>±0</td><td>301</td><td>0/4</td></tr>
<tr><td>0/4</td><td>301</td><td style="background:#FFE">30-40</td><td>±0</td><td>301</td><td>0/4</td></tr>
<tr><td>0/4</td><td>301</td><td style="background:#FFE">30-40</td><td style="background:#FEE">-1</td><td>300</td><td>0/4</td></tr>
<tr><td>0/4</td><td>300</td><td style="background:#FFE">15-40</td><td style="background:#FEE">-7</td><td>293</td><td>0/4</td></tr>
<tr><td>0/4</td><td>293</td><td style="background:#FFE">15-40</td><td style="background:#FEE">-7</td><td>286</td><td>0/4</td></tr>
<tr><td>0/4</td><td>286</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-15</td><td>271</td><td>0/4</td></tr>
<tr><td>0/4</td><td>271</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-13</td><td>258</td><td>0/4</td></tr>
<tr><td>0/4</td><td>258</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-13</td><td>245</td><td>0/4</td></tr>
<tr><td>0/4</td><td>245</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-12</td><td>233</td><td>0/4</td></tr>
<tr><td>0/4</td><td>233</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-12</td><td>221</td><td>0/4</td></tr>
<tr><td>0/4</td><td>221</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-11</td><td>210</td><td>0/4</td></tr>
<tr><td>0/4</td><td>210</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-11</td><td>199</td><td>0/4</td></tr>
<tr><td>0/4</td><td>199</td><td style="background:#FFE">15-40</td><td style="background:#FEE">-2</td><td>197</td><td>0/4</td></tr>
<tr><td>0/4</td><td>197</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-10</td><td>187</td><td>0/4</td></tr>
<tr><td>0/4</td><td>187</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-9</td><td>178</td><td>0/4</td></tr>
<tr><td>0/4</td><td>178</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-9</td><td>169</td><td>0/4</td></tr>
<tr><td>0/4</td><td>169</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-9</td><td>160</td><td>0/4</td></tr>
<tr><td>0/4</td><td>160</td><td style="background:#FFE">0-40</td><td style="background:#FEE">-8</td><td>152</td><td>0/4</td></tr>
<tr><td>0/4</td><td>152</td><td style="background:#FFE">15-40</td><td>±0</td><td>152</td><td>0/4</td></tr>
<tr><td>0/4</td><td>152</td><td style="background:#FFE">15-40</td><td>±0</td><td>152</td><td>0/4</td></tr>
<tr><td>0/4</td><td>152</td><td style="background:#FFE">15-40</td><td>±0</td><td>152</td><td>0/4</td></tr>
<tr><td>0/4</td><td>152</td><td style="background:#FFE">15-40</td><td>±0</td><td>152</td><td>0/4</td></tr>
<tr><td>0/4</td><td>152</td><td style="background:#FFE">15-40</td><td>±0</td><td>152</td><td>0/4</td></tr>
<tr><td>0/4</td><td>152</td><td style="background:#FFE">15-40</td><td>±0</td><td>152</td><td>0/4</td></tr>
<tr><td>0/4</td><td>152</td><td style="background:#FFE">15-40</td><td style="background:#FEE">-1</td><td>151</td><td>0/4</td></tr>
<tr><td>0/4</td><td>151</td><td style="background:#FFE">15-40</td><td>±0</td><td>151</td><td>0/4</td></tr>
<tr><td>0/4</td><td>151</td><td style="background:#FFE">15-40</td><td>±0</td><td>151</td><td>0/4</td></tr>
<tr><td>0/4</td><td>151</td><td style="background:#FFE">15-40</td><td>±0</td><td>151</td><td>0/4</td></tr>
<tr><td>0/4</td><td>151</td><td style="background:#FFE">15-40</td><td>±0</td><td>151</td><td>0/4</td></tr>
<tr><td>0/4</td><td>151</td><td style="background:#FFE">15-40</td><td>±0</td><td>151</td><td>0/4</td></tr>
<tr><td>0/4</td><td>151</td><td style="background:#FFE">15-40</td><td>±0</td><td>151</td><td>0/4</td></tr>
<tr><td>0/4</td><td>151</td><td style="background:#FFE">15-40</td><td>±0</td><td>151</td><td>0/4</td></tr>
<tr><td>0/4</td><td>151</td><td style="background:#FFE">15-40</td><td>±0</td><td>151</td><td>0/4</td></tr>
<tr><td>0/4</td><td>151</td><td style="background:#FFE">15-40</td><td>±0</td><td>151</td><td>0/4</td></tr>
<tr><td>0/4</td><td>151</td><td style="background:#FFE">15-40</td><td>±0</td><td>151</td><td>0/4</td></tr>
<tr><td>0/4</td><td>151</td><td style="background:#FFE">15-40</td><td>±0</td><td>151</td><td>0/4</td></tr>
<tr><td>0/4</td><td>151</td><td style="background:#FFE">15-40</td><td style="background:#FEE">-1</td><td>150</td><td>0/4</td></tr>
</tbody></table>
<p>At this point, I thought that the asymptote was <em>multiplied</em> by a factor depending on the rival's skill, and that that factor would be 2 for Sarah/Elisa, giving 2400. But the next experiment proved me wrong: the differences between the asymptotes for different scores were not doubled, but remained the same. That suggested that they were offset instead. But first I wanted to be sure that the 0/4 skill rivals were reached losing by with 40-Adv. This particular series can be quite valuable to help finding out how the rivals ranking works.
</p>
<table class="linetable center" cellspacing="0" cellpadding="0"><caption>Table 8: Series of 40-Adv losses followed by a series of Adv-40 wins.
</caption><thead>
<tr><th>Rivals<br />skill</th><th>Skill<br />before</th><th>Score</th><th>Points</th><th>Skill<br />after</th><th>Next<br />rivals<br />skill</th></tr>
</thead><tbody>
<tr><td>63/49</td><td>0</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+20</td><td>20</td><td>57/35</td></tr>
<tr><td>57/35</td><td>20</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+20</td><td>40</td><td>46/23</td></tr>
<tr><td>46/23</td><td>40</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+18</td><td>58</td><td>33/12</td></tr>
<tr><td>33/12</td><td>58</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+17</td><td>75</td><td>24/12</td></tr>
<tr><td>24/12</td><td>75</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+17</td><td>92</td><td>17/4</td></tr>
<tr><td>17/4</td><td>92</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+15</td><td>107</td><td>12/0</td></tr>
<tr><td>12/0</td><td>107</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+13</td><td>120</td><td>9/0</td></tr>
<tr><td>9/0</td><td>120</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+14</td><td>134</td><td>6/0</td></tr>
<tr><td>6/0</td><td>134</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+13</td><td>147</td><td>4/0</td></tr>
<tr><td>4/0</td><td>147</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+13</td><td>160</td><td>3/4</td></tr>
<tr><td>3/4</td><td>160</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+11</td><td>171</td><td>2/4</td></tr>
<tr><td>2/4</td><td>171</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+12</td><td>183</td><td>2/4</td></tr>
<tr><td>2/4</td><td>183</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+11</td><td>194</td><td>1/4</td></tr>
<tr><td>1/4</td><td>194</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+10</td><td>204</td><td>1/4</td></tr>
<tr><td>1/4</td><td>204</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+9</td><td>215</td><td>1/4</td></tr>
<tr><td>1/4</td><td>215</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+10</td><td>225</td><td>0/4</td></tr>
<tr><td>0/4</td><td>225</td><td style="background:#FFE">40-Adv</td><td style="background:#EFE">+8</td><td>233</td><td>0/4</td></tr>
<tr><td>0/4</td><td>233</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+29</td><td>262</td><td>2/4</td></tr>
<tr><td>2/4</td><td>262</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+27</td><td>289</td><td>6/0</td></tr>
<tr><td>6/0</td><td>289</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+25</td><td>314</td><td>14/4</td></tr>
<tr><td>14/4</td><td>314</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+24</td><td>338</td><td>26/12</td></tr>
<tr><td>26/12</td><td>338</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+24</td><td>362</td><td>44/23</td></tr>
<tr><td>44/23</td><td>362</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+22</td><td>384</td><td>66/49</td></tr>
<tr><td>66/49</td><td>384</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+21</td><td>405</td><td>92/65</td></tr>
<tr><td>92/65</td><td>405</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+21</td><td>426</td><td>120/100</td></tr>
<tr><td>120/100</td><td>426</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+20</td><td>446</td><td>160/140</td></tr>
<tr><td>160/140</td><td>446</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+20</td><td>466</td><td>200/160</td></tr>
<tr><td>200/160</td><td>466</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+19</td><td>485</td><td>240/210</td></tr>
<tr><td>240/210</td><td>485</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+20</td><td>505</td><td>290/260</td></tr>
<tr><td>290/260</td><td>505</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+19</td><td>524</td><td>340/310</td></tr>
<tr><td>340/310</td><td>524</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+19</td><td>543</td><td>400/370</td></tr>
<tr><td>400/370</td><td>543</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+20</td><td>563</td><td>460/430</td></tr>
<tr><td>460/430</td><td>563</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+20</td><td>583</td><td>520/490</td></tr>
<tr><td>520/490</td><td>583</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+21</td><td>604</td><td>580/550</td></tr>
<tr><td>580/550</td><td>604</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+21</td><td>625</td><td>640/620</td></tr>
<tr><td>640/620</td><td>625</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+22</td><td>647</td><td>710/690</td></tr>
<tr><td>710/690</td><td>647</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+23</td><td>670</td><td>780/760</td></tr>
<tr><td>780/760</td><td>670</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+23</td><td>693</td><td>860/840</td></tr>
<tr><td>860/840</td><td>693</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+25</td><td>718</td><td>930/910</td></tr>
<tr><td>930/910</td><td>718</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+26</td><td>744</td><td>1000/990</td></tr>
<tr><td>1000/990</td><td>744</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+27</td><td>771</td><td>1100/1100</td></tr>
<tr><td>1100/1100</td><td>771</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+28</td><td>799</td><td>1200/1200</td></tr>
<tr><td>1200/1200</td><td>799</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+29</td><td>828</td><td>1300/1200</td></tr>
<tr><td>1300/1200</td><td>828</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+31</td><td>859</td><td>1300/1300</td></tr>
<tr><td>1300/1300</td><td>859</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+33</td><td>892</td><td>1400/1400</td></tr>
<tr><td>1400/1400</td><td>892</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+33</td><td>925</td><td>1500/1500</td></tr>
<tr><td>1500/1500</td><td>925</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+36</td><td>961</td><td>1600/1600</td></tr>
<tr><td>1600/1600</td><td>961</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+37</td><td>998</td><td>1700/1700</td></tr>
<tr><td>1700/1700</td><td>998</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+38</td><td>1036</td><td>1600/1600(*)</td></tr>
<tr><td>1600/1600(*)</td><td>1036</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+41</td><td>1077</td><td>1900/1900</td></tr>
<tr><td>1900/1900</td><td>1077</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+42</td><td>1119</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1119</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+43</td><td>1162</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1162</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+42</td><td>1204</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1204</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+40</td><td>1244</td><td>2000/1900</td></tr>
<tr><td>2000/1900</td><td>1244</td><td style="background:#EFF">Adv-40</td><td style="background:#EFE">+38</td><td>1282</td><td>2000/1900</td></tr>
</tbody></table>
<p class="noindent">(*) That is probably 1800/1800 and is a transcription error on my side. Sometimes I confuse the 6s and the 8s with that font.
</p>
<p>With a Libreoffice spreadsheet I was able to determine the asymptote from just the last few data points, when the rivals were maxed out. It turned out to be 2000.
</p>
<p>It's easy to construct the spreadsheet. In cell B2 put the starting value of the series you want to analyze. In cell A2 put the asymptote to test. Then in cell B3 write this formula: =($A$2+C3+B2*19)/20. Column C will be for offsets (a series of 0's for 0/4 rivals, a series of 1200's for 2000/1900 rivals, and you can adjust by hand cell by cell in other cases). Then extend B3 to the rest of column B up to where you want. Then in cell D2 you write: =INT(B2) and extend it to the rest of column D. If you want, in cell E2 you can put: =(D3-D2) and extend it to the column; that column will have the computed differences. You can also add an extra column F with the real differences or with the real skill points, starting at F2, and a column G with this formula: =IF(E2<>F2;"Diff";"=") to tell you which cells don't match (change E2 to D2 if you put real skill points in column F instead of differences).
</p>
<p>So, that's what I have so far. If you are willing to help me out with the rivals' skill points formula, please state so in the comments. Thanks!
</p>
pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com2tag:blogger.com,1999:blog-7525171767194694125.post-40915670148697147992013-04-24T20:15:00.001+02:002013-04-24T20:41:20.449+02:00Caps Lock Led Toggled problem<p>The cause: <a href="https://bugs.freedesktop.org/show_bug.cgi?id=16145">https://bugs.freedesktop.org/show_bug.cgi?id=16145</a>
</p>
<p>The solution: <code style="background:#e0e8e8;padding:0px 2px">xdotool key Caps_Lock</code>
</p>
<p>Another alternative solution is to use xmacro: <code style="background:#e0e8e8;padding:0px 2px">printf 'KeyStrPress Caps_Lock\nKeyStrRelease Caps_Lock\n' | xmacroplay $DISPLAY</code>
</p>
<p>Basically, the reason is that when faking a <kbd>Caps Lock</kbd> key press programmatically in order for an application to toggle the Caps Lock state, the led does not toggle and the state becomes out of sync between the led and the keyboard.
</p>
<p>It used to happen when entering a word like tHIS in OpenOffice with Caps Lock on, though late versions seem to have fixed it. But I've also suffered it when using x11vnc and pressing <kbd>Caps Lock</kbd> in the client. At that moment, the keyboard becomes desynced. The command above syncs it again. The source code in the bug report linked above works well too, in case you can't find xdotool or xmacro. They come as Debian and Ubuntu packages, though, so they should be easy to find.
</p>
pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com0tag:blogger.com,1999:blog-7525171767194694125.post-70057861981939190032013-04-22T13:22:00.000+02:002013-04-24T19:47:50.795+02:00Laboratorio de evolución<p>¿Qué hace falta para que la evolución se produzca?</p>
<p>Hacen falta organismos autorreplicantes. Es decir, algún tipo de entidad que pueda producir copias de sí misma. Por ejemplo, células, animales, plantas, pero sirven otras entidades. Dichas copias deberían ser inexactas, es decir, las réplicas deben ser parecidas pero no iguales al progenitor. Si son idénticas, no puede haber evolución.</p>
<p>Ha de haber un medio al que adaptarse (si los organismos ya están adaptados, poca evolución se puede dar). Las variaciones en las réplicas deben tener el potencial de que algunas están mejor adaptadas al medio (aunque sólo sea levemente) y otras peor.</p>
<p>Y hace falta tiempo, claro. La evolución no es un proceso instantáneo.</p>
<p>En el escenario típico en el que la evolución se produce, el medio suele tener alimento que los organismos requieren. Suele variar con el tiempo debido a factores muy diversos como climatología, ciertos eventos o fenómenos, o la propia interacción con los organismos. Éstos, por su parte, requieren adaptarse al medio para conseguir alimento; y el fracaso al hacerlo influye en su mortalidad, que repercute en la capacidad reproductiva.</p>
<p>Aquí presentamos un programa en JavaScript que pueden ejecutar en su navegador, que incluye todos esos elementos para demostrar cómo la evolución se produce de forma natural en él. Está basado en un programa en Pascal escrito por Javier Sebastián y refinado por Alejandro Valero y un servidor, que a su vez está basado en la descripción de un programa escrito por Michael Palmiter <a href="http://lifesciassoc.home.pipeline.com/instruct/evolution/">[1]</a> que apareció en un artículo de A.K.Dewdney, en la sección Juegos de Ordenador de la revista Investigación y Ciencia.</p>
<p>La versión JavaScript aquí presentada, así como el presente texto, son, por supuesto, copyright © 2013 Pedro Gimeno Fortea. El generador de números pseudoaleatorios es <em>ran_array</em>, un generador de Fibonacci retardado (LFG) con retardos 37 y 100, diseñado por Donald E. Knuth.</p>
<p>El programa utiliza el elemento <code><canvas></code>, por lo que es necesario un navegador que lo soporte. Si su navegador es muy antiguo, es posible que no funcione; no hay como probar para saberlo.</p>
<p>¿En qué consiste la simulación? Se trata de un «mundo» rectangular en el que habitan unos organismos, que llamaremos protozoos, que pueden moverse libremente (la topología es toroidal, es decir, cuando un protozoo sale por arriba, reentra por abajo en el mismo punto, y similarmente con izquierda y derecha). En este «mundo» aparecen unas motas que llamaremos bacterias, que por simplicidad lo hacen por generación espontánea y permanecen en el sitio hasta que son comidas por un protozoo, si es que ocurre. Si se prefiere, en vez de bacterias se puede imaginar que se trata de polvo alimenticio que se acumula en una superficie. La velocidad de aparición de las bacterias es constante. Una zona del rectángulo es un «jardín del Edén», donde las bacterias florecen a un ritmo mucho más rápido. En su centro hay un foco luminoso.</p>
<p>Los protozoos se reproducen por bipartición y se alimentan de bacterias al pasar por encima de ellas (si no están saciados). Para poder reproducirse, deben tener una energía mínima necesaria y haber alcanzado una edad mínima de «madurez sexual». Cuando se dan ambas circunstancias, se produce la bipartición sin más demora. El tiempo se mide en «ciclos» o «ticks» de un reloj imaginario. La vida de un protozoo está limitada por dos factores: tiempo (es decir, pueden morir de viejos) y energía (muerte por inanición).</p>
<p>Por simplicidad, la única variación que hay en la reproducción de los protozoos afecta a la forma en que se mueven. Cada protozoo contiene un «material genético» que define la probabilidad de que haga ciertos movimientos. La retícula por la que se mueven (y en la que se colocan las bacterias) tiene una disposición hexagonal. Eso implica seis sentidos diferentes en los que se puede mover cada organismo.</p>
<p>Los protozoos tienen una «dirección actual» que cambia en cada ciclo de una forma aleatoria pero modulada por el material genético. Cada posible cambio de dirección tiene un gen asociado: hay un gen para seguir adelante, otro para girar 60° a la derecha, otro para hacerlo a la izquierda, otro para girar 120° a la derecha, otro para lo mismo a la izquierda, y otro para dar media vuelta. Además, hay un gen que indica si el protozoo es atraído por la luz. Si se activa, el protozoo gira inmediatamente en la dirección que más le acercaría a la luz. Si hay más de una que le acercaría más que las demás, se escoge una al azar de entre las posibles.</p>
<p>Este material genético está en forma de enteros. Dado lo restringido de la simulación, para que ésta sea más rápida, los enteros se aplican de forma exponencial para decidir el giro siguiente. Para decidir en qué forma girará el protozoo, el valor de 2 elevado a cada gen se usa como peso en una elección aleatoria. Por ejemplo, si el gen de avanzar tiene el valor 3 y todos los demás tienen el valor 0, entonces es ocho (2³) veces más probable que el protozoo avance que que haga cada uno de los otros movimientos.</p>
<p>La simulación empieza con cierta población inicial de protozoos y de bacterias. Los genes se precargan con números al azar. Al principio, típicamente se mueven de forma tan errática e indecisa que, a la larga, apenas se desplazan de su posición original. Esto es ineficiente en ese medio, ya que pronto consumirán las bacterias que tienen a su alrededor, se quedarán sin alimento y morirán. Pero unos pocos tienen algo más de movilidad y consiguen sobrevivir unos ciclos más. Los más afortunados sobrevivirán lo suficiente como para que, ante el declive del número de depredadores que se llevan el alimento, la zona se llene de bacterias y puedan comer mucho más rápido, llegando a alcanzar la energía de reproducción.</p>
<p>En cada reproducción se produce una mutación (un cambio de una unidad, al alza o a la baja, en un gen al azar). Como en cualquier proceso evolutivo, las mutaciones pueden ser buenas o malas. Si la progenie está peor
adaptada, morirá más facilmente, y si se adapta mejor al medio, lo que en este caso implica tener más movilidad para alcanzar más alimento, tendrá más facilidad para sobrevivir y por tanto reproducirse.</p>
<p>Y eso es lo que se observa en la simulación. Aunque no tiene ninguna preferencia para ser elegido en el código, con el transcurrir de las generaciones, el gen de ir en línea recta predomina sobre los demás, ya que esto facilita al protozoo desplazarse alrededor del área en vez de «enquistarse» en una zona más pequeña, lo cual le supone una ventaja sobre otros protozoos que no tienen esa característica. Y al comer más, también deja menos comida para los peor adaptados, haciendo, por tanto, más facil que mueran de inanición.</p>
<p>Esto se observa con las semillas aleatorias de 1 a 3. Con la semilla 4, sucede algo diferente. Algunos de los protozoos empiezan a tener «curiosidad» por la luz; cuando llegan, descubren que por allí hay comida en abundancia. Tímidos todavía, siguen mayormente haciendo lo único que saben hacer, que es moverse erráticamente, pero si se alimentan lo suficiente, parte de su descendencia desarrollará mayor afinidad por la luz, con lo que tendrá más acceso al jardín del Edén. Teniendo sus necesidades cubiertas, aventurarse a las proximidades en busca de otros métodos para alimentarse va a resultar contraproducente. Mientras tanto, los que no han desarrollado una afinidad por la luz suficiente, siguen su curso. El resultado es la especiación: dos especies de protozoos, una afín a la luz que ronda el jardín del Edén, que llamaremos Polilla, y la otra afanosa por cubrir la mayor área posible, que llamaremos Explorador. Y, como en la evolución natural, esta especiación es producto del azar.</p>
<p>Una vez las dos especies se han diferenciado, es difícil que se produzca un cambio. Si uno de los genes empieza a cobrar importancia, el otro se reducirá en importancia, con lo que en este mundo competitivo, tendrá menos ventaja sobre sus hermanos. Así, es poco probable que la descendencia de una Polilla acabe transformándose en Explorador, porque los genes no mutan tan deprisa, y una vez empieza a separarse de la luz, el alimento escasea demasiado en comparación; similarmente, es poco probable que la descendencia de un Explorador acabe siendo Polilla, porque en cuanto la predisposición a ir en línea recta decrece para dar importancia a girar hacia la luz, sus hermanos Exploradores darán cuenta del alimento y el individuo estará en desventaja competitiva, haciendo más fácil que muera antes de evolucionar. Sin embargo, ese tipo de saltos de comportamiento han sido observados durante el ajuste de parámetros, lo cual implica que no son imposibles.</p>
<p>Además de permitirnos contemplar cómo, con los ingredientes adecuados, la evolución es un proceso que surge de forma natural, este programa también tiene otro efecto secundario. Nos ofrece también la posibilidad de observar el fenómeno de la fluctuación en las poblaciones de un sistema depredador-presa, del tipo modelado por las ecuaciones diferenciales de <a href="http://en.wikipedia.org/wiki/Lotka%E2%80%93Volterra_equation">Lotka–Volterra</a>. En este caso, los depredadores son los protozoos y las presas son las bacterias. Cuando el número de protozoos aumenta, éstos comen bacterias y hacen que la población de bacterias disminuya. Esa disminución pronto provoca muerte por inanición de protozoos, con lo que la población de éstos empieza a disminuir a su vez. Al haber menos depredadores, las bacterias tienen oportunidad de volver a crecer en número, lo que hace que los protozoos tengan más comida, con lo que su número aumenta y el ciclo se repite. No ha sido fácil encontrar parámetros que hacen la relación lo bastante estable; durante la búsqueda, en algunos casos he encontrado un <em>baby boom</em> que causa una superpoblación que arrasa con la gran mayoría de bacterias, seguido de una caída en picado de la población que acaba con todos los protozoos antes de que las bacterias se recuperen en número.</p>
<p>Así que, he aquí el programa. Que lo disfruten.</p>
<form id="params" name="params" method="get" action="#">
<noscript><p style="color:red">Este programa está implementado en JavaScript y no lo tienes activo, lo que significa que no puedes ejecutarlo.</p></noscript>
<script type="text/javascript">
<!--
var ev_xsize = 240;
var ev_ysize = 240;
var ev_popht = 160; // population graph height
var ev_ms = 20;
var ev_baczoomout = 35; // reduction factor for bacteria population in graph
var ev_bkgc = "#000000";
var ev_bacc = "#c80000";
var ev_proc = "#00c8c8";
var ev_ligc = "#ffffff";
var ev_pzpc = "#009090";
var ev_bcpc = "#900000";
var ev_pbgc = "#ffffff";
var ev_evo;
var ev_running;
var ev_timeout;
var ev_popctx;
function ev_GenerateCanvas()
{
document.write('\n\x3Cdiv>\x3Ccanvas id="canvas" width="' + (ev_xsize*2+1) + '" height="' + (ev_ysize*2+2) + '">\x3C/canvas>\x3C/div>\n');
var canvas = document.getElementById("canvas");
if (canvas && canvas.getContext)
{
var ctx = canvas.getContext("2d");
ctx.fillStyle = ev_bkgc;
ctx.fillRect(0, 0, ev_xsize*2+1, ev_ysize*2+2);
return true;
}
return false;
}
// Random number generation and initialization
var ev_RandCurIndex;
var ev_RandState = new Array(100);
function ev_GetRan()
{
var result = ev_RandState[ev_RandCurIndex];
ev_RandState[ev_RandCurIndex] = (ev_RandState[ev_RandCurIndex] + 0x40000000 - ev_RandState[(ev_RandCurIndex + 63) % 100]) & 0x3FFFFFFF;
if (++ev_RandCurIndex >= 100) ev_RandCurIndex = 0;
return result;
}
function ev_RandStart(seed)
{
var ss = (seed + 2) & 0x3FFFFFFE;
var n;
var t = 69;
var x = new Array(199);
ev_RandCurIndex = 0;
for (n = 0; n < 100; ++n)
{
x[n] = ss;
ss += ss;
if (ss > 0x3FFFFFFF) ss -= 0x3FFFFFFE;
}
for (; n < 199; ++n) x[n] = 0;
++x[1];
ss = seed & 0x3FFFFFFF;
do
{
for (n = 99; n; n -= 1) x[n+n] = x[n];
for (n = 198; n > 63; n -= 2) x[199 - n] = x[n] & 0x3FFFFFFE;
for (n = 198; n >= 100; n -= 1)
{
if (x[n] & 1)
{
x[n - 63] = (x[n - 63] + 0x40000000 - x[n]) & 0x3FFFFFFF;
x[n - 100] = (x[n - 100] + 0x40000000 - x[n]) & 0x3FFFFFFF;
}
}
if (ss & 1)
{
for (n = 100; n; n -= 1) x[n] = x[n-1];
x[0] = x[100];
if (x[100] & 1) x[37] = (x[37] + 0x40000000 - x[100]) & 0x3FFFFFFF;
}
if (ss) ss = ss >> 1; else t -= 1;
}
while (t);
for (n = 0; n < 37; ++n) ev_RandState[n + 63] = x[n];
for (; n < 100; ++n) ev_RandState[n - 37] = x[n];
}
// Protozoan object
function ev_Protozoan(InitialStrength)
{
this.Age = 0;
this.Strength = InitialStrength;
this.Dir = ev_GetRan() % 6;
}
// Evolver object
function ev_Evolver()
{
this.ctx = document.getElementById("canvas").getContext("2d");
this.popgraph = document.getElementById("popgraph");
this.popctx = this.popgraph.getContext("2d");
this.population = document.getElementById("population");
this.steps = 0;
this.bacpop = 0;
this.popctx.fillStyle = ev_pbgc;
this.popctx.fillRect(0, 0, ev_xsize+ev_xsize-1, ev_popht);
var i;
var j;
this.bacteria = new Array(ev_xsize);
for (i = 0; i < ev_xsize; ++i)
{
this.bacteria[i] = new Array(ev_ysize);
}
ev_RandStart(parseInt(document.forms.params.randseed.value));
// Parameters
/*
this.InitialBacteria = 5000;
this.ReproductionAge = 500;
this.ReproductionStrength = 1000;
this.MaxAge = 10000;
this.MaxStrength = 4000;
this.NewbornStrength = 500;
this.BacteriaFood = 30;
this.EdenX = 4;
this.EdenY = 4;
this.LightX = 180;
this.LightY = 180;
*/
this.InitialBacteria = ev_xsize*ev_ysize/20;
this.InitialProtozoans = 30;
this.ReproductionAge = 500;
this.ReproductionStrength = 1000;
this.MaxAge = 10000;
this.MaxStrength = 4000;
this.NewbornStrength = 500;
this.BacteriaFood = 30;
this.EdenX = 3;
this.EdenY = 3;
this.LightX = ev_xsize/2;
this.LightY = ev_ysize/2;
this.FieldGrowthRate = 1.5; // 1 means 1 pixel per step with 320,240 field
this.EdenGrowthRate = .5; // 1 means 1 pixel per step with 4,4 eden
this.ticksperscroll = 400;
this.ticks = 0;
this.FieldRunningDensity = 0;
this.EdenRunningDensity = 0,
this.FieldArea = ev_xsize*ev_ysize;
this.EdenArea = (this.EdenX*2+1)*(this.EdenY*2+1);
this.ctx.fillStyle = ev_bacc;
for (i = 0; i < this.InitialBacteria; ++i)
{
var x = ev_GetRan() % ev_xsize;
var y = ev_GetRan() % ev_ysize;
if (! this.bacteria[x][y]) ++this.bacpop;
this.bacteria[x][y] = true;
this.ctx.fillRect(x+x+1, y+y+(x & 1)+1, 1, 1);
}
this.ctx.fillStyle = ev_proc;
this.protozoans = new Array();
for (i = this.InitialProtozoans; i; i -= 1)
{
var pro = new ev_Protozoan(this.NewbornStrength);
// Starting DNA
pro.LeftGene = ev_GetRan() % 5;
pro.RightGene = ev_GetRan() % 5;
pro.HardLeftGene = ev_GetRan() % 5;
pro.HardRightGene = ev_GetRan() % 5;
pro.FwdGene = ev_GetRan() % 5;
pro.BackGene = ev_GetRan() % 5;
pro.LightGene = ev_GetRan() % 5 - 5; // Light affinity is too powerful as implemented - we compensate a bit
do
{
pro.PosX = ev_GetRan() % ev_xsize;
pro.PosY = ev_GetRan() % ev_ysize;
}
while (typeof this.bacteria[pro.PosX][pro.PosY] == 'undefined'); // find an empty spot
this.ctx.fillRect(pro.PosX*2, pro.PosY*2+(pro.PosX & 1), 3, 3);
this.protozoans.push(pro);
}
this.population.innerHTML = this.protozoans.length + "/" + this.bacpop;
this.promax = this.promin = this.protozoans.length;
this.bacmax = this.bacmin = this.bacpop;
ev_running = true;
if (ev_timeout)
clearTimeout(ev_timeout);
ev_timeout = setTimeout(ev_DoSteps, ev_ms);
}
function ev_ConsiderPosition(rec, evo, pro, xofs, yofs, newdir)
{
// Our metric is a bit unorthodox due to the hexagonal grid.
// We convert coordinates from odd-even to uniform in order to obtain the distance to light
var xdif = pro.PosX + xofs - evo.LightX;
var ydif = pro.PosY - (((pro.PosX + xofs + 2) >> 1) - 1) + yofs - (evo.LightY - (evo.LightX >> 1));
var dist = Math.abs(xdif + ydif);
if ((xdif < 0) ^ (ydif < 0)) // different signs?
{
// if so, distance is this one instead:
dist = Math.max(Math.abs(xdif), Math.abs(ydif));
}
if (rec.mindist === null || dist <= rec.mindist)
{
if (dist < rec.mindist)
rec.divisor = 0;
if (ev_GetRan() % ++rec.divisor == 0)
{
rec.mindist = dist;
rec.dir = newdir;
}
}
}
function ev_Step()
{
// Draw new bacteria
var bacx;
var bacy;
var evo = ev_evo;
evo.ctx.fillStyle = ev_bacc;
evo.FieldRunningDensity += evo.FieldArea * evo.FieldGrowthRate;
while (evo.FieldRunningDensity >= 76800)
{
evo.FieldRunningDensity -= 76800;
bacx = ev_GetRan() % ev_xsize;
bacy = ev_GetRan() % ev_ysize;
if (!evo.bacteria[bacx][bacy]) ++evo.bacpop;
evo.bacteria[bacx][bacy] = true;
evo.ctx.fillRect(bacx+bacx+1, bacy+bacy+1+(bacx & 1), 1, 1);
}
evo.EdenRunningDensity += evo.EdenArea * evo.EdenGrowthRate;
while (evo.EdenRunningDensity >= 81)
{
evo.EdenRunningDensity -= 81;
bacx = ev_GetRan() % (evo.EdenX*2+1) - evo.EdenX + evo.LightX;
bacy = ev_GetRan() % (evo.EdenY*2+1) - evo.EdenY + evo.LightY;
if (!evo.bacteria[bacx][bacy]) ++evo.bacpop;
evo.bacteria[bacx][bacy] = true;
evo.ctx.fillRect(bacx+bacx+1, bacy+bacy+1+(bacx & 1), 1, 1);
}
var pro;
// update max and min # of protozoans and bacteria, for graph
if (evo.protozoans.length > evo.promax) evo.promax = evo.protozoans.length;
if (evo.protozoans.length < evo.promin) evo.promin = evo.protozoans.length;
if (evo.bacpop > evo.bacmax) evo.bacmax = evo.bacpop;
if (evo.bacpop < evo.bacmin) evo.bacmin = evo.bacpop;
++evo.steps;
// Graph population
if (++evo.ticks >= evo.ticksperscroll)
{
evo.ticks = 0;
evo.popctx.drawImage(evo.popgraph, -1, 0);
// clearRect creates a transparent background that doesn't scroll properly with drawImage.
// We have to use fillRect here.
evo.popctx.fillStyle = ev_pbgc;
evo.popctx.fillRect(ev_xsize+ev_xsize-2, 0, 1, ev_popht);
evo.population.innerHTML = evo.protozoans.length + "/" + evo.bacpop;
// Draw graph for this iteration
evo.popctx.fillStyle = ev_pzpc;
evo.popctx.fillRect(ev_xsize+ev_xsize-2, ev_popht-1-evo.promax, 1, evo.promax - evo.promin + 1);
evo.popctx.fillStyle = ev_bcpc;
evo.popctx.fillRect(ev_xsize+ev_xsize-2, ev_popht-1-evo.bacmax / ev_baczoomout, 1, (evo.bacmax - evo.bacmin) / ev_baczoomout + 1);
// The graph already included this iteration, and after scrolling, it will restart on this iteration,
// therefore the lines will always be connected horizontally.
evo.promax = evo.promin = evo.protozoans.length;
evo.bacmax = evo.bacmin = evo.bacpop;
}
// Handle dying protozoans and update positions
var i = evo.protozoans.length;
while (i)
{
i -= 1;
pro = evo.protozoans[i];
if (++pro.Age >= evo.MaxAge || (pro.Strength -= 1) < 0)
{
evo.ctx.fillStyle = ev_bkgc;
evo.ctx.fillRect(pro.PosX*2, pro.PosY*2+(pro.PosX & 1), 3, 3);
evo.protozoans.splice(i, 1);
evo.population.innerHTML = evo.protozoans.length + "/" + evo.bacpop;
}
else
{
pro.OldX = pro.PosX;
pro.OldY = pro.PosY;
pro.PosX += (pro.Dir == 1 || pro.Dir == 2) - (pro.Dir == 4 || pro.Dir == 5);
pro.PosY += (pro.Dir == 3 || ((pro.Dir == 2 || pro.Dir == 4) && !(pro.PosX & 1))) -
(pro.Dir == 0 || ((pro.Dir == 1 || pro.Dir == 5) && (pro.PosX & 1)));
if (pro.PosX < 0) pro.PosX += ev_xsize;
if (pro.PosX >= ev_xsize) pro.PosX -= ev_xsize;
if (pro.PosY < 0) pro.PosY += ev_ysize;
if (pro.PosY >= ev_ysize) pro.PosY -= ev_ysize;
}
}
// Draw protozoans
for (i in evo.protozoans)
{
pro = evo.protozoans[i];
evo.ctx.fillStyle = ev_bkgc;
evo.ctx.fillRect(pro.OldX*2, pro.OldY*2+(pro.OldX & 1), 3, 3);
if (evo.bacteria[pro.OldX][pro.OldY])
{
// Redraw bacteria if erased
evo.ctx.fillStyle = ev_bacc;
evo.ctx.fillRect(pro.OldX*2+1, pro.OldY*2+1+(pro.OldX & 1), 1, 1);
}
evo.ctx.fillStyle = ev_proc;
evo.ctx.fillRect(pro.PosX*2, pro.PosY*2+(pro.PosX & 1), 3, 3);
}
// (Re)Draw light
evo.ctx.fillStyle = ev_ligc;
evo.ctx.fillRect(evo.LightX*2, evo.LightY*2+(evo.LightX & 1), 3, 3);
// Feed, reproduce, mutate, turn
for (i in evo.protozoans)
{
pro = evo.protozoans[i];
if (evo.bacteria[pro.PosX][pro.PosY] && pro.Strength < evo.MaxStrength) // don't eat if satiated
{
// Feed
delete evo.bacteria[pro.PosX][pro.PosY];
evo.bacpop -= 1;
pro.Strength += evo.BacteriaFood;
}
if (pro.Age >= evo.ReproductionAge && pro.Strength >= evo.ReproductionStrength)
{
// Reproduce
var newpro = new ev_Protozoan(evo.NewbornStrength);
pro.Age = 0;
pro.Strength = newpro.Strength = evo.NewbornStrength;//pro.Strength >> 1;
for (var prop in pro) if (prop != "Dir") newpro[prop] = pro[prop];
// Mutate
var gene = ev_GetRan() % 7;
var inc = ((ev_GetRan() & 1) << 1) - 1;
pro.LeftGene += (gene == 0) * inc;
pro.RightGene += (gene == 1) * inc;
pro.HardLeftGene += (gene == 2) * inc;
pro.HardRightGene += (gene == 3) * inc;
pro.FwdGene += (gene == 4) * inc;
pro.BackGene += (gene == 5) * inc;
pro.LightGene += (gene == 6) * inc;
gene = ev_GetRan() % 7;
inc = ((ev_GetRan() & 1) << 1) - 1;
newpro.LeftGene += (gene == 0) * inc;
newpro.RightGene += (gene == 1) * inc;
newpro.HardLeftGene += (gene == 2) * inc;
newpro.HardRightGene += (gene == 3) * inc;
newpro.FwdGene += (gene == 4) * inc;
newpro.BackGene += (gene == 5) * inc;
newpro.LightGene += (gene == 6) * inc;
// Store child
evo.protozoans.push(newpro);
evo.population.innerHTML = evo.protozoans.length + "/" + evo.bacpop;
}
// Turn according to genetical predisposition
var min = Math.min(pro.LeftGene, pro.RightGene, pro.HardLeftGene, pro.HardRightGene, pro.FwdGene, pro.BackGene, pro.LightGene);
var LG = 1 << (pro.LeftGene - min);
var RG = 1 << (pro.RightGene - min);
var HLG = 1 << (pro.HardLeftGene - min);
var HRG = 1 << (pro.HardRightGene - min);
var FG = 1 << (pro.FwdGene - min);
var BG = 1 << (pro.BackGene - min);
var LtG = 1 << (pro.LightGene - min);
var Sum = LG + RG + HLG + HRG + FG + BG + LtG;
if (Sum > 0x40000000) Sum = 0x40000000;
var Rnd = ev_GetRan() % Sum;
var turn = -1;
if (Rnd >= LtG) ++turn;
if (Rnd >= LtG + FG) ++turn;
if (Rnd >= LtG + FG + RG) ++turn;
if (Rnd >= LtG + FG + RG + HRG) ++turn;
if (Rnd >= LtG + FG + RG + HRG + BG) ++turn;
if (Rnd >= LtG + FG + RG + HRG + BG + HLG) ++turn;
if (turn < 0)
{
// Light tendency: turn towards light.
// Six points to consider. Which one(s) is (are) closest to the light?
var rec = {dir: 3, mindist: null, divisor: 0};
ev_ConsiderPosition(rec, evo, pro, 0, -1, 0);
ev_ConsiderPosition(rec, evo, pro, 1, -1, 1);
ev_ConsiderPosition(rec, evo, pro, 1, 0, 2);
ev_ConsiderPosition(rec, evo, pro, 0, 1, 3);
ev_ConsiderPosition(rec, evo, pro, -1, 1, 4);
ev_ConsiderPosition(rec, evo, pro, -1, 0, 5);
// All positions considered, we now have a new dir
pro.Dir = rec.dir;
}
else
{
pro.Dir = (pro.Dir + turn) % 6;
}
}
if (ev_running)
{
if (ev_timeout)
clearTimeout(ev_timeout);
ev_timeout = setTimeout(ev_DoSteps, ev_ms);
}
}
function ev_Go()
{
document.forms.params.pausebutton.value = "Pausa";
var ctx = document.getElementById("canvas").getContext("2d");
ctx.fillStyle = ev_bkgc;
ctx.fillRect(0, 0, ev_xsize*2+1, ev_ysize*2+2);
ev_evo = new ev_Evolver();
}
function ev_DoSteps()
{
ev_Step();
ev_Step();
ev_Step();
ev_Step();
ev_Step();
ev_Step();
ev_Step();
ev_Step();
}
function ev_Pause()
{
if (ev_running)
{
ev_running = false;
if (ev_timeout)
{
clearTimeout(ev_timeout);
ev_timeout = false;
}
document.forms.params.pausebutton.value = "Continuar";
}
}
function ev_Resume()
{
if (! ev_running)
{
ev_running = true;
if (ev_timeout)
clearTimeout(ev_timeout);
ev_timeout = setTimeout(ev_DoSteps, ev_ms);
document.forms.params.pausebutton.value = "Pausa";
}
}
function ev_PauseResume()
{
if (ev_running)
{
ev_Pause();
}
else
{
ev_Resume();
}
}
function ev_RandomizeSeed()
{
document.forms.params.randseed.value = parseInt(Math.random()*0x40000000);
}
if (ev_GenerateCanvas())
{
document.write(
'\x3Cdiv\x3E\x3Ccanvas id="popgraph" width="'+ (ev_xsize*2-1) +'" height="' + ev_popht + '" style="border:solid 1px black"\x3E\x3C/canvas\x3E\x3C/div\x3E\n'+
'\x3Cdiv\x3EPoblación: \x3Cspan id="population"\x3E\x3C/span\x3E\x3C/div\x3E\n'+
'\x3Ctable\x3E\n'+
'\x3Ctr\x3E\x3Ctd\x3ESemilla aleatoria:\x3C/td\x3E\x3Ctd\x3E\x3Cinput type="text" name="randseed" size="20" value="1"\x3E\x3Cinput type="button" name="generatebutton" value="Escoger una al azar" onclick="ev_RandomizeSeed();"\x3E\x3C/td\x3E\x3C/tr\x3E\n'+
'\x3Ctr\x3E\x3Ctd colspan="2"\x3E\x3Cinput type="button" name="startbutton" value="(Re)Comenzar" onclick="ev_Go();"\x3E'+
'\x3Cinput type="button" name="pausebutton" value="Pausa" onclick="ev_PauseResume();"\x3E'+
'\x3Cinput type="button" name="stepbutton" value="Paso a paso" onclick="ev_Pause(); ev_Step();"\x3E\x3C/td\x3E\x3C/tr\x3E\n'+
'\x3C/table\x3E\n'
);
}
else
{
document.write('\x3Cp\x3EThis browser does not support canvas extensions.\x3C/p\x3E');
}
//-->
</script>
</form>
<h4>Referencias</h4>
<p>[1] Michael Palmiter, <em>Simulated Evolution</em>. <a href="http://lifesciassoc.home.pipeline.com/instruct/evolution/">http://lifesciassoc.home.pipeline.com/instruct/evolution/</a>
</p>
pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com1tag:blogger.com,1999:blog-7525171767194694125.post-16723316248248154832011-08-23T16:32:00.002+02:002013-04-24T19:47:49.618+02:00<p>Si te gusta el té fuerte, he aquí un simple consejo: no le pongas azúcar o leche hasta que el té se haya disuelto por completo. Esto favorecerá que el agua tenga menos soluto, permitiéndole así disolver más cantidad de té.
</p>pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com1tag:blogger.com,1999:blog-7525171767194694125.post-9559578951331911292011-08-19T14:05:00.000+02:002013-04-24T19:47:51.214+02:00Nostalgia - Los programadores de verdad no usan Pascal<p>Me he reencontrado recientemente con este texto en mis archivos. Releerlo ha sido una experiencia bastante nostálgica. He creído conveniente reproducirlo en versión castellana. La <a href="http://www.pbm.com/~lindahl/real.programmers.html">versión inglesa</a> está todavía disponible en línea, así que ofrezco aquí mi traducción. Parece ser que fue publicado en 1983. Helo aquí en toda su gloria:
</p>
<blockquote>
<h3>Los Programadores de Verdad No Usan Pascal</h3>
<p>En los buenos tiempos, la "Edad de Oro" de los ordenadores, era fácil
separar a los hombres de verdad de los críos (a veces en la literatura se les
ha llamado "Hombres de Verdad" y "Comedores de Pastelitos"). Durante ese
periodo, los Hombres de Verdad eran los que comprendían la programación de
ordenadores y los Comedores de Pastelitos eran los que no. Un auténtico
programador de ordenadores decía cosas como:
</p>
<pre class="code">
DO 10 I = 1, 10
</pre>
y
<pre class="code">
ABEND
</pre>
<p>Hablaban en mayúsculas, ya me entienden. El resto del mundo decía cosas
como "los ordenadores son demasiado complicados para mí", y "no puedo
relacionarme con ordenadores: son tan impersonales...". Un escrito anterior
(1) puntualiza que los Hombres de Verdad no se relacionan con nada, y no temen
ser impersonales. Pero, como suele pasar, los tiempos cambian. Hoy nos
enfrentamos a un mundo en el que las señoras tienen ordenadores en su horno de
microondas, los niños de 12 años pueden vencer a los Hombres de Verdad jugando
al Asteroids o al Pac-Man, y cualquiera puede comprar y comprender su propio
ordenador personal. El Programador de Verdad está en peligro de extinción, en
peligro de ser reemplazado por estudiantes universitarios con Commodores y
Ataris. Hay una clara necesidad de puntualizar las diferencias entre un típico
jugador junior de Pac-Man de universidad y el Programador de Verdad. Si se
aclara esta diferencia, les dará a esos muchachos algo a lo que aspirar - un
modelo, una figura paterna. También ayudará a explicar al que tenga
contratado a un Programador de Verdad por qué sería un error reemplazar a los
Programadores de Verdad de su plantilla por <em>jugadores-de-PacMan-de-12-años</em>
(que le suponen un ahorro considerable).
</p>
<h4>1.1 Lenguajes</h4>
<p>La forma más fácil de distinguir a un Programador de Verdad del resto es
por el lenguaje de programación que él o ella usa. Los Programadores de Verdad
usan Fortran. Los Comedores de Pastelitos usan Pascal. Nicklaus Wirth, el
diseñador del Pascal, dio una conferencia una vez en la que le preguntaron:
"¿Cómo pronuncia usted su nombre?" El respondió: "Pueden llamarme por mi
nombre, pronunciándolo 'Virt', o pueden llamarme por mi valor, 'Worth'"
[valioso, merecedor - N.del T.]. Enseguida se deduce de ese comentario que
Nicklaus Wirth es un Comedor de Pastelitos. El único mecanismo de pase de
parámetros que usan los Programadores de Verdad es la "llamada por valor -
retorno", como se implementa en los compiladores de FORTRAN G y H del IBM/370.
Los Programadores de Verdad no necesitan todos esos conceptos abstractos para
hacer su trabajo: son perfectamente felices con un teclado perforador, un
compilador FORTRAN IV, y una cerveza.
</p>
<ul>
<li>Los Programadores de Verdad realizan Proceso de Listas en FORTRAN.<br />
[N.del T.: Proceso de Listas = List Processing, de donde viene el nombre del
lenguaje de programación LISP]</li>
<li>Los Programadores de Verdad realizan Manipulación de Cadenas en FORTRAN.</li>
<li>Los Programadores de Verdad hacen Contabilidad (si es que la hacen) en
FORTRAN.</li>
<li>Los Programadores de Verdad hacen programas de Inteligencia Artificial en
FORTRAN.</li>
</ul>
<p class="noindent"> Si no puedes hacerlo en FORTRAN, hazlo en Lenguaje Ensamblador, o no
merecerá la pena hacerlo.
</p>
<h4>1.2 Programación Estructurada</h4>
<p>Los académicos de la ciencia de computación se han hecho esclavos de la
"Programación Estructurada" durante los últimos años. Afirman que los
programas son más fácilmente comprensibles si el programador utiliza ciertas
construcciones especiales del lenguaje, por supuesto, y los ejemplos que usan
para mostrar su punto de vista caben invariablemente en una sola hoja de
alguna que otra oscura publicación, ejemplo claramente insuficiente para
convencer a nadie. Cuando salí de la escuela, pensaba que era el mejor
programador del mundo. Podía escribir un programa invencible de tres en raya,
usar cinco lenguajes de ordenador diferentes, y crear programas de 1000 líneas
que funcionaban (¡de verdad!). Entonces salí al Mundo Real. Mi primer trabajo
fue leer y comprender un programa en FORTRAN de 20.000 líneas, para acelerarlo
al doble de su velocidad. Cualquier Programador de Verdad te dirá que toda la
Programación Estructurada del mundo no te ayudará a solucionar un problema
como ese: requiere auténtico talento. Algunas observaciones a bote pronto
sobre los Programadores de Verdad y la Programación Estructurada:
</p>
<ul>
<li>Los Programadores de Verdad no temen usar GOTO.</li>
<li>Los Programadores de Verdad pueden escribir bucles DO de cinco páginas sin
confundirse.</li>
<li>Los Programadores de Verdad disfrutan con las sentencias IF aritméticas:
hacen el código más interesante.</li>
<li>Los Programadores de Verdad escriben código automodificable, especialmente si
pueden ahorrar 20 nanosegundos en mitad de un bucle crítico.</li>
<li>Los Programadores de verdad no necesitan comentarios: el código es obvio.</li>
</ul>
<p class="noindent">Aunque el FORTRAN no tiene sentencias IF, REPEAT...UNTIL ni CASE estructuradas, los Programadores de Verdad no se preocupan por no poder usarlos. Además, todas
esas estructuras pueden ser simuladas mediante GOTOs cuando es necesario.
</p>
<p>Las estructuras de datos también han recibido mucha atención últimamente.
Los Tipos Abstractos, Estructuras, Punteros, Listas y Cadenas se han hecho muy
populares en ciertos círculos. Nicklaus Wirth (el antes mencionado Comedor de
Pastelitos) ha conseguido escribir todo un libro (2) defendiendo que se puede
escribir un programa basándose en Estructuras de Datos en lugar de todo lo
habitual. Como todos los Programadores de Verdad saben, la única estructura de
datos útil es el ARRAY. Las cadenas, listas, estructuras, conjuntos... son
sólo casos especiales de Arrays y pueden ser tratados de esa forma fácilmente
sin necesidad de ensuciar el lenguaje de programación con todo tipo de
complicaciones. Lo peor sobre los tipos de datos bonitos es que tienes que
declararlos, y todos los Lenguajes de Programación de Verdad, como todos
sabemos, tienen tipos implícitos basados en la primera letra del nombre (de
seis caracteres) de la variable.
</p>
<h4>1.3 Sistemas Operativos</h4>
<p>¿Qué tipo de sistema operativo usa el Programador de Verdad? ¿CP/M? Por
favor, el CP/M, después de todo, es básicamente un sistema operativo de
juguete. Hasta las señoras respetables y los estudiantes de primaria pueden
usar y entender el CP/M. El UNIX es mucho más complicado, por supuesto: el
típico hacker de UNIX nunca es capaz de recordar cómo se llama el comando de
imprimir esta semana. Cuando se le conoce bien, el UNIX no es sino un video
juego glorificado. La gente no hace trabajo serio en sistemas UNIX: envían
chistes por el mundo a través de una red UUCP, y escriben juegos de aventuras
y artículos de investigación. No, el Programador de Verdad usa OS/370. Un buen
programador puede encontrar en el manual del JCL y comprender la descripción de
un mensaje de error IJK3051 que acaba de aparecerle. Un gran programador puede
escribir JCL sin recurrir al manual del JCL en absoluto. Un programador
realmente bueno puede encontrar bugs enterrados en un volcado hexadecimal de
seis Megabytes sin usar una calculadora hexadecimal. El OS/370 es un sistema
operativo realmente destacable. Es posible destruir días de trabajo con un
simple espacio fuera de lugar (nos ocurre hasta a los mejores), así que se
favorece la alerta entre el personal de programación. La forma de acercarse
más al sistema es mediante un teclado perforador. Hay gente que afirma que hay
un sistema de Compartición de Tiempos que funciona en OS/370, pero tras un
cuidadoso estudio he llegado a la conclusión de que estaban equivocados.
</p>
<h5 class="noindent">Referencias:</h5>
<p class="noindent">
(1) Fierstein, B., Real Men Don't Eat Quiche, New York, Pocket Books, 1982
<br />(2) Wirth, N., Algorithms+Data Structures=Programs, Prentice Hall, 1976
</p>
<h4>1.4 Herramientas de Programación</h4>
<p>¿Qué tipo de herramientas usa un programador de verdad? En teoría, un
programador de verdad podría ejecutar sus programas tecleándolos en el panel
frontal de un ordenador. En los tiempos en los que los ordenadores tenían
paneles frontales, esto se hacía en alguna ocasión. El típico programador de
verdad se conocía de memoria la rutina de arranque en hexadecimal, y la
reemplazaba cuando su programa destruía el arranque. Por entonces, la memoria
era memoria: no se iba al quitar la alimentación. Hoy, la memoria bien olvida
cosas que no quieres, o recuerda demasiado tiempo lo que sería mejor olvidar.
Dice la leyenda que Seymour Cray (el inventor del superordenador Cray-1 y la
mayoría de los ordenadores de Control Data) tecleó el primer sistema operativo
del CDC-7600 en el panel frontal de memoria la primera vez que fue puesto en
marcha. Seymour, por supuesto, es un programador de verdad.
</p>
<p>Uno de mis programadores de verdad favoritos era un programador de
sistemas de Texas Instruments. Un día, recibió una llamada de larga distancia
de un usuario cuyo sistema se había venido abajo a mitad de salvar un trabajo
importante. Jim fue capaz de reparar el daño por teléfono, consiguiendo que el
usuario metiera las instrucciones de E/S de disco en el panel frontal,
reparando las tablas del sistema en hexadecimal, leyendo los contenidos de los
registros a través del teléfono. La moraleja de la historia: aunque un
programador de verdad normalmente incluye un teclado perforador y una
impresora de líneas entre sus herramientas, puede desenvolverse bien con sólo
un panel frontal y un teléfono en caso de emergencia.
</p>
<p>En algunas compañías, la edición de textos ya no consiste en diez
ingenieros esperando en fila para usar un teclado perforador 029. De hecho, el
edificio en que trabajo no tiene ni un solo teclado perforador. El programador
de verdad, al verse en esa situación, tiene que trabajar con un programa de
edición de textos. La mayoría de los sistemas proporcionan varios editores de
textos entre los que seleccionar, y el programador de verdad debe tener
cuidado de elegir el que más refleja su estilo personal. Mucha gente cree que
los mejores editores de texto del mundo se escribieron en el Centro de
Investigación Xerox de Palo Alto para usarse en sus ordenadores Alto y Dorado
(3). Por desgracia, ningún programador de verdad usaría un ordenador con un
sistema operativo llamado SmallTalk, y desde luego nunca le hablaría a un
ordenador con un ratón.
</p>
<p>Algunos de los conceptos de esos editores de Xerox han sido incorporados
en editores que funcionan en sistemas más razonables, por ejemplo EMACS y VI.
El problema con dichos editores es que los programadores de verdad consideran
que "lo que ves es lo que hay" [también llamado WYSIWYG - N.del T.] es un
concepto tan malo aplicado a los editores como lo es aplicado a las mujeres.
No, el programador de verdad prefiere un editor tipo "tú lo has pedido, ahí lo
tienes": complicado, críptico, potente, inolvidable, y peligroso. TECO para
ser preciso.
</p>
<p>Se ha observado que una secuencia de comandos de TECO se parece más al
ruido de transmisión de línea que a texto legible (4). Un entretenido juego
que se puede jugar con TECO es escribir tu nombre en la línea de comandos
e intentar adivinar lo que hará. Casi cualquier error de escritura durante la
comunicación con TECO destruirá probablemente tu programa, o aún peor,
introducirá bugs misteriosos y sutiles en una subrutina que antes funcionaba.
</p>
<p>Por esta razón, los programadores de verdad se muestran recelosos a la
hora de editar un programa que está próximo a funcionar. Encuentran mucho más
fácil editar el código objeto en binario directamente, usando un maravilloso
programa llamado SUPERZAP. Este sistema funciona tan bien que muchos programas
en funcionamiento en sistemas IBM no mantienen relación alguna con su código
FORTRAN original. En muchos casos el código fuente original ya no está
disponible. Cuando llega el momento de modificar un programa como ese, ningún
director pensaría siquiera en enviar a cualquiera que no fuera un programador
de verdad para hacer el trabajo. Ningún Programador Estructurado Comedor De
Pastelitos sabría siquiera por dónde empezar. A esto se le llama Seguridad del
Puesto.
</p>
<p>He aquí algunas herramientas de programación que los programadores de
verdad no usan:
</p>
<ul>
<li>Preprocesadores de FORTRAN como MORTRAN o RATFOR. Son como las cocinas de
la programación: ideales para hacer pastelitos. Ver arriba los comentarios
sobre programación estructurada.</li>
<li>Debuggers de Código Fuente. Los Programadores de Verdad pueden leer
volcados hexadecimales.</li>
<li>Compiladores con chequeo de límite en arrays. Ahogan la creatividad,
destruyen la mayoría de los usos interesantes de la sentencia EQUIVALENCE, y
hacen imposible modificar el sistema operativo mediante subíndices negativos.
Y lo peor de todo, el chequeo de límite es ineficiente.</li>
<li>Sistemas de mantenimiento de código fuente. Un programador de verdad
mantiene su código cerrado con llave en un fichero de tarjetas, porque eso
implica que el dueño no puede dejar programas importantes al descubierto (5).</li>
</ul>
<p class="noindent">
(3) Xerox PARC Editors...<br />
(4) Finseth, C. Theorey and Practice of Text Editors - or A Cookbook for an
EMACS, B.S. Thesis, MIT/LCS/TM-165, Massachusetts Institute of Technology, May
1980.<br />
(5) Weinberg, G. The Psychology of Computer Programming, New York, Van
Nostrand Reinhold, 1971, p. 110.
</p>
<h4>1.5 El Programador de Verdad en el trabajo</h4>
<p>¿Dónde trabaja el típico programador de verdad? ¿Qué tipo de programas merecen
los esfuerzos de un individuo con tanto talento? Puede estar seguro de que
no encontrará a ningún programador de verdad escribiendo programas de
contabilidad en COBOL, u ordenando listas de correo para la revista People. Un
programador de verdad quiere tareas de una importancia que haga temblar la
tierra (¡literalmente!).
</p>
<p>Hay Programadores de Verdad trabajando para el Laboratorio Nacional de
Los Alamos escribiendo simulaciones de bomba atómica para superordenadores
CRAY-1. Hay Programadores de Verdad trabajando para la Agencia de Seguridad
Nacional, decodificando transmisiones rusas.
</p>
<p>Fue gracias sobre todo al esfuerzo de miles de Programadores de Verdad
que trabajaban para la NASA que los americanos llegaron a la luna antes que
los rusos. Hay Programadores de Verdad trabajando para Boeing, diseñando
sistemas operativos para misiles crucero.
</p>
<p>Algunos de los más puros Programadores de Verdad de todos trabajan en el
Jet Propulsion Laboratory de California. Muchos de ellos se saben el sistema
operativo completo de las naves Pioneer y Voyager de memoria. Con la ayuda de
grandes programas en FORTRAN desde tierra y pequeños programas en ensamblador
desde la nave espacial, son capaces de hacer increíbles hazañas de navegación
y de previsión: acertar en ventanas de diez kilómetros de anchura en Saturno
tras seis años en el espacio, reparar o prescindir de plataformas sensoras,
radios o baterías dañadas. Se cuenta que un programador de verdad consiguió
meter un programa de comparación de patrones en unos cuantos cientos de bytes
de memoria no usada en una nave espacial Voyager que buscó, localizó y
fotografió una nueva luna de Júpiter. Los planes actuales de la nave espacial
Galileo son usar una trayectoria asistida por gravedad a través de Marte en su
camino hacia Júpiter. Esta trayectoria pasa a 80 más menos 3 kilómetros de la
superficie de Marte. Nadie va a confiar en un programa en Pascal (o en un
programador de Pascal, para el caso) para navegar con esas tolerancias.
</p>
<p>Como puede imaginar, muchos de los Programadores de Verdad del mundo trabajan para
el Gobierno americano, sobre todo en el Departamento de Defensa. Así es como
debe ser. Desde hace poco, sin embargo, se ha formado una nube negra en el horizonte
de los Programadores de Verdad. Parece que algunos Comedores de Pastelitos con
un puesto importante en el Departamento de Defensa decidieron que todos los
programas de Defensa deberían ser escritos en un gran lenguaje unificado
llamado ADA. Durante un tiempo parecía que el ADA estaba destinado a
convertirse en un lenguaje que iba contra todos los preceptos de la
programación de verdad: un lenguaje con estructura, con tipos de datos, mucho
tecleo, y puntos y comas. En definitiva, un lenguaje diseñado para inutilizar
la creatividad del típico programador de verdad. Por suerte, el lenguaje que
adoptaron tenía las suficientes cualidades como para hacerlo aprovechable: es
increíblemente complejo, incluye métodos para cargarse el sistema operativo y
reposicionar memoria, y a Edsger Dijkstra no le gusta (6). Dijkstra, como
seguramente ya sabrá, es el autor de "el Go To considarado peligroso", que
marcó época en la metodología de programación y fue aplaudido por
programadores en Pascal y comedores de pastelitos. Y de todas formas, un
programador de verdad puede escribir programas FORTRAN en cualquier
lenguaje.
</p>
<p>Los Programadores de Verdad podrían comprometer sus principios y trabajar
en algo ligeramente más trivial que la destrucción o la vida, si hay
suficiente dinero implicado. Hay varios programadores de verdad escribiendo
video juegos en Atari, por ejemplo (pero no jugando a ellos: un programador de
verdad sabe cómo ganar en cualquier momento; eso no es un reto para ellos).
Todos los de LucasFilm son programadores de verdad (sería de locos rechazar el
dinero de cincuenta millones de fans de Star Trek). La proporción de
programadores de verdad en gráficos por ordenador es ligeramente menor de lo
normal, sobre todo porque nadie les ha encontrado un uso a los gráficos por
ordenador todavía. Por otra parte, toda la programación de gráficos por
ordenador está hecha en FORTRAN, así que hay cierto número de ellos dedicados
a los gráficos sólo para evitar escribir programas en COBOL.
</p>
<h4>1.6 El Programador de Verdad Jugando</h4>
<p>Generalmente, el programador de verdad juega de la misma forma que
trabaja: con ordenadores. El programador de verdad está constantemente
asombrado de que su jefe le pague por lo que él haría de todas formas (aunque
tiene cuidado de no expresar esta opinión en voz alta). Ocasionalmente, un
programador de verdad sale fuera de la oficina a tomar un respiro de aire
fresco y una cerveza o dos. He aquí algunas pistas para reconocer a los
programadores de verdad fuera de la sala de ordenadores:
</p>
<ul>
<li>En una fiesta, los programadores de verdad son los que están en la
esquina hablando sobre seguridad de sistemas y cómo saltársela.</li>
<li>En un partido de fútbol, el programador de verdad es el que compara las
jugadas con la simulación impresa en papel continuo de 11 por 14.</li>
<li>En la playa, el programador de verdad es el que está dibujando diagramas
de flujo en la arena.</li>
<li>En un funeral, el programador de verdad es el que dice "Pobre George. Y
eso que casi había conseguido hacer que su rutina de ordenación funcionara
antes de su coronaria".</li>
<li>En la tienda de ultramarinos, el programador de verdad es el que insiste
en pasar él mismo las latas por el lector de barras, porque nunca confiaría en
que un operador de teclado perforador lo hiciera bien la primera vez.</li>
</ul>
</blockquote>pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com0tag:blogger.com,1999:blog-7525171767194694125.post-58300144716951680662011-01-13T18:06:00.002+01:002013-04-24T19:47:50.628+02:00Piloto automático para naves espaciales<div class="math">
<p>Algún afortunado lector quizá haya jugado con el sensacional <a href="http://www.iancgbell.clara.net/elite/"><em>Elite</em></a> de Ian Bell y David Braben. o sus aún más sensacionales continuaciones, <em>Elite II: The Frontier</em> y <em>Frontier: First Encounters.</em> Los tres son simuladores espaciales. El primero no tiene un motor físico muy realista: en <em>Elite</em>, la velocidad no es relativa. Hay un sistema de referencia universal, y la nave tiene una velocidad máxima dentro de ese sistema de referencia.
</p>
<p>En cambio, en los otros dos el motor de física es mucho más realista. La velocidad actual está indicada siempre con respecto a un sistema de referencia dado, que generalmente se corresponde con el astro o planeta más próximo, y puede cambiar cuando éste lo hace. Los motores de las naves pueden acelerar y frenar, lo que impone una aceleración máxima, como es lógico, pero no hay limitación para la velocidad. Como pega en contra de ese realismo, aunque a favor de la jugabilidad, cabe nombrar que la aceleración de la nave no depende de la masa de la misma, lo cual sí que ocurriría si los motores ejercieran una fuerza de empuje máxima (<i>F=ma</i>). Otros detalles, aunque de menos importancia, son la falta de efectos relativistas y la omnidireccionalidad del empuje, así como el hecho de que la aceleración a la que la nave es sometida (típicamente rondando los 20 <em>g</em>) no podría ser soportada por un cuerpo humano. Le concedemos esas licencias de buena gana, porque el producto resultante bien lo vale.
</p>
<p>Como los dos títulos comparten la palabra <em>Frontier</em> y no hay diferencias entre ellos en la parte que aquí nos interesa, me referiré a ambos colectivamente usando dicha palabra. <em>Frontier</em> cuenta con un piloto automático, cuyo principio de funcionamiento exacto desconozco, para viajar de un punto a otro del espacio. Sin embargo, yo al menos soy fan de desconectar el piloto automático y volar a mano, porque disfruto haciéndolo, y porque el piloto automático utiliza el motor delantero para frenar, que siempre tiene menos potencia, mientras que yo doy la vuelta a la nave para hacerlo y así puedo alcanzar una velocidad mayor y frenar a tiempo. (Es un error típico de novatos el acelerar continuamente hasta llegar al destino... y pasarse de largo por mucho, porque la potencia de frenado es la misma que la de aceleración y por lo tanto requiere el mismo tiempo acelerar que frenar).
</p>
<p>Cuando quiero ir de punto a punto, observo la distancia al objetivo; entonces acelero hasta la mitad del trayecto y decelero hasta llegar. Los resultados suelen ser bastante buenos; en la primera aproximación quizá no me quede lo bastante cerca, pero con una o dos aproximaciones más (cada vez más cortas) el éxito está prácticamente garantizado.
</p>
<p>Sin embargo, las batallas suelen aparecer en medio del viaje, y cuando lo hacen, me estropean los planes de navegación, porque debido a la inercia que llevo, voy acercándome al objetivo mientras dura la batalla, lo cual, cuando se va a 12.000 km/s, resulta significativo. Esto me hizo preguntarme: ¿y cómo calcular entonces el punto de frenada, para llegar cuanto antes al objetivo? La respuesta resulta no ser muy fácil para el caso de una dimensión. Cuando se da la eventualidad de una batalla, los movimientos laterales son pequeños, lo que permite considerar el caso 1D como una buena aproximación.
</p>
<p>El método entonces sería el siguiente. El espacio necesario para frenar dada la velocidad actual viene dado por la fórmula <i>v</i>²/(2<i>a</i>), donde <i>a</i> es la aceleración máxima con la que podemos frenar y <i>v</i> la velocidad con la que se reanuda el viaje. Por razones que me costaría explicar, el punto de frenada viene dado por (<i>x</i><sub>0</sub> - <i>v</i>|<i>v</i>|/(2<i>a</i>))/2, donde <i>x</i><sub>0</sub> es el punto inicial, y el punto destino se asume 0. Puede ser que ya nos hayamos pasado de dicho punto, en cuyo caso es inevitable que nos pasemos de largo y tengamos que frenar hasta pararnos.
</p>
<p>Pero, ¿qué pasa si estoy de camino hacia un planeta y de repente cambio de opinión y quiero dirigirme a otro? En ese caso, tengo una velocidad inicial cuya alineación probablemente no coincida para nada con el destino final, por lo cual no podemos usar el truco de 1D. ¿Qué puedo hacer, si quiero llegar cuanto antes?
</p>
<p>Anticipo que no sé la respuesta. Si para el caso 1D no era fácil, para 3D es diabólico. Esto es lo que sé. Con un cambio de sistema de referencia, siempre se puede transformar el problema en uno en dos dimensiones. Se formularía así: dados un punto inicial, <i>p</i><sub>0</sub>, una velocidad inicial, <i>v</i><sub>0</sub>, y una aceleración máxima, <i>a<sub>max</sub></i>, determinar la función que da la aceleración a aplicar en cada instante, tal que se alcance el punto (0, 0) a velocidad nula en el menor tiempo posible.
</p>
<p>O, alternativamente, partiendo parados desde el punto (0, 0), hallar la función de aceleración necesaria para alcanzar el punto <i>p<sub>F</sub></i> a una velocidad <i>v<sub>F</sub></i> (como vector), en el mínimo tiempo posible. Es lo mismo pero haciendo el camino al revés e invirtiendo la velocidad.
</p>
<p>Es un problema frustrante porque, pese a su simple planteamiento y aspecto inocente, resulta bastante esquivo. La aproximación más inmediata, que es resolverlo en 1D para cada eje, no funciona porque al aplicar la aceleración máxima por cada eje, el módulo de la aceleración es mayor que la aceleración máxima. Podemos dividir la aceleración por √<span style="text-decoration:overline">2</span> para compensar, pero es que no es la única pega: nada nos garantiza que el tiempo que se tarda en cada eje coincida. Así que tenemos además que encontrar una aceleración máxima por eje, que cumpla dos requisitos: que el tiempo sea el mismo en cada eje, y que la suma de los cuadrados de las aceleraciones máximas coincida con el cuadrado de la aceleración máxima dada.
</p>
<p>¿Es óptima esa solución? Pues una de dos: o no lo es, o hay infinitas soluciones óptimas. Para demostrarlo, basta ver que si rotamos el sistema de referencia en el problema original, deberíamos obtener una versión rotada de la solución, cosa que no ocurre. Los cuatro posibles vectores de la solución forman siempre un rectángulo alineado con los ejes.
</p>
<p>En fin, ando quebrándome la cabeza con este problema.
</p>
</div>pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com3tag:blogger.com,1999:blog-7525171767194694125.post-30401221431993494412010-10-08T16:04:00.001+02:002013-04-24T19:47:49.868+02:00Colección de Simon Tatham: Range<p>Un nuevo puzle ha sido añadido a la <a href="http://www.chiark.greenend.org.uk/~sgtatham/puzzles/">colección de Simon Tatham</a>. Se llama <em>Range</em> y es interesante. Las reglas pueden recordar un poco al <em>Singles</em> en algunos aspectos, pero en realidad al jugarlo encontramos reminiscencias del <em>Pattern</em> más bien.
</p>
<p>Las reglas no están muy bien explicadas en la documentación, así que voy a dar una explicación aquí. Nos presentan un casillero con varios números colocados en varias casillas. Nuestro objetivo es determinar qué casillas hay que rellenar de negro, cumpliendo las siguientes reglas:
</p>
<ul>
<li>Todas las casillas que no son negras tienen que formar una figura conexa, es decir, no se permite aislar una casilla blanca o un grupo conexo de casillas blancas en una «isla». Se entiende que están conectadas por arriba, abajo, izquierda o derecha; las diagonales no cuentan.</li>
<li>No puede haber dos cuadrados negros adyacentes (igualmente, por arriba, abajo, izquierda o derecha).</li>
<li>Las casillas con números no se pueden pintar de negro.</li>
<li>Cada número indica la cantidad de casillas totales, incluida ella misma, que pueden alcanzarse hasta tropezar con una casilla negra, moviéndose hacia arriba, abajo, izquierda o derecha a partir de la casilla donde está el número.</li>
</ul>
<p>Por ejemplo, en el tablero por defecto de 9×6, si aparece un 14, eso quiere decir que no hay ninguna casilla negra en la fila ni en la columna donde aparece dicho número, pues hay 8 casillas horizontales y 5 casillas verticales además de la propia casilla, en total 14. Si aparece un 13, quiere decir que hay una casilla negra en uno de los cuatro extremos, no sabemos en principio en cuál, y todas las demás son blancas.
</p>
<p>En la documentación pone como ejemplo el número 1. Si apareciera un 1, al contarse a sí misma ya estaría el cupo lleno, por lo tanto ese 1 debería estar rodeado de casillas negras. Pero eso formaría una región de una casilla que estaría aislada del resto de casillas blancas, quebrantando otra de las reglas. Por lo tanto, no puede aparecer un 1 en ningún tablero.
</p>
<p>Como ayuda para resolver los problemas, podemos marcar casillas con un punto que indica «esta casilla no es negra». Así, por ejemplo, en cuanto colocamos una casilla negra podemos rodearla de puntos para recordarnos que ahí no puden ir otras casillas negras, de acuerdo con las reglas.
</p>
<p>Como de costumbre, la solución de cada problema es única. El grado de adicción lo compararía con el del <em>Pattern</em> también. Podría decirse que <em>Range (Kurodoko)</em> es a <em>Pattern (Nonograms, Picross)</em> como <em>Unequal (Futoshiki)</em> es a <em>Solo (Sudoku)</em>.
</p>pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com0tag:blogger.com,1999:blog-7525171767194694125.post-73227035932931071152010-08-14T00:00:00.006+02:002013-04-24T19:47:50.879+02:00<p>Enhorabuena, acaban ustedes de sobrevivir a otro fin del mundo.
</p>pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com0tag:blogger.com,1999:blog-7525171767194694125.post-84846946992487631822010-08-12T19:19:00.000+02:002013-04-24T19:47:51.173+02:00Batallitas<p><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaqCVkgOWyKvQueriMgwG2GcqUOk8gJ3QAbR1gvu3IEaafz9G8ourxTrEKL5V76Lixu41WBd7VH529QqmUKx-6wOFpG9KaajIhzLyDH5Eibm3s8rrWeF7OQlYwWIUAtXkhqVFy2xITP80/s800/Historias-de-guerra-1970-vs-2040.png" alt="Batallitas - Cómic" />
</p>pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com0tag:blogger.com,1999:blog-7525171767194694125.post-42209799169499029272010-08-11T21:00:00.002+02:002013-04-24T19:47:49.993+02:00<p>Las psicofonías son el arte de decirte lo que has de oír. Y es que, cuando te dicen lo que has de oír, lo oyes esté o no ahí. Un ejemplo:
</p>
<div><object width="480" height="385"><param name="movie" value="http://www.youtube.com/v/hOgALTFzFbQ&hl=en_US&fs=1"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/hOgALTFzFbQ&hl=en_US&fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="480" height="385"></embed></object></div>
<p>(La canción está en Hindi, su título original es Mehboob Mere y pertenece a la película Fiza; la letra real en Hindi —o mejor dicho, una transcripción a nuestro alfabeto— está por ejemplo aquí: <a href="http://www.hindilyrix.com/songs/get_song_Mehboob%20mere,%20mehboob%20mere.html">http://www.hindilyrix.com/songs/get_song_Mehboob%20mere,%20mehboob%20mere.html</a>).</p>pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com0tag:blogger.com,1999:blog-7525171767194694125.post-56495706826115274422010-03-12T16:50:00.000+01:002013-04-24T19:47:50.461+02:00Frases para el recuerdo<p><i>En los tiempos de los apostóles
<br />los hombres eran muy barbáros:
<br />se subían a los arbóles
<br />y se comían los pajáros.</i>
</p>
<p>(Tildes añadidas para enfatizar la pronunciación).
</p>pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com2tag:blogger.com,1999:blog-7525171767194694125.post-84011776738321973262010-02-24T21:42:00.004+01:002013-04-24T19:47:51.131+02:00Solución al problema de la cebra<p>Vamos con la solución al <a href="/2010/02/quien-es-el-dueno-de-la-cebra.html">puzle de la cebra</a>.
</p>
<p>Recordemos las pistas:</p>
<ol>
<li>1=B5</li>
<li>3=E3</li>
<li>A1=D2</li>
<li>A2=B1</li>
<li>A5=E1</li>
<li>B2=E4</li>
<li>B3=D1</li>
<li>B4=C5</li>
<li>C4=D3</li>
<li>D4=E5</li>
<li>A4|B5</li>
<li>C1|D5</li>
<li>C3|D2</li>
<li>A3|A5</li>
<li>A3<A5</li>
</ol>
<p>Ponemos en un casillero los símbolos, de tal forma que en cada casilla estén todos los símbolos de una fila. Para esto se puede emplear OOCalc, que es lo que he hecho.
</p>
<table border="1" style="text-align:center">
<thead><th></th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th></thead>
<tbody>
<tr><th>A</th><td>A1A2A3A4A5</td><td>A1A2A3A4A5</td><td>A1A2A3A4A5</td><td>A1A2A3A4A5</td><td>A1A2A3A4A5</td></tr>
<tr><th>B</th><td>B1B2B3B4B5</td><td>B1B2B3B4B5</td><td>B1B2B3B4B5</td><td>B1B2B3B4B5</td><td>B1B2B3B4B5</td></tr>
<tr><th>C</th><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td></tr>
<tr><th>D</th><td>D1D2D3D4D5</td><td>D1D2D3D4D5</td><td>D1D2D3D4D5</td><td>D1D2D3D4D5</td><td>D1D2D3D4D5</td></tr>
<tr><th>E</th><td>E1E2E3E4E5</td><td>E1E2E3E4E5</td><td>E1E2E3E4E5</td><td>E1E2E3E4E5</td><td>E1E2E3E4E5</td></tr>
</tbody>
</table>
<p>Aplicamos las pistas 1, 2 y 11 para hallar la posición de B5, E3 y A4.
</p>
<table border="1" style="text-align:center">
<thead><th></th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th></thead>
<tbody>
<tr><th>A</th><td>A1A2A3A4A5</td><td>A4</td><td>A1A2A3A4A5</td><td>A1A2A3A4A5</td><td>A1A2A3A4A5</td></tr>
<tr><th>B</th><td>B5</td><td>B1B2B3B4B5</td><td>B1B2B3B4B5</td><td>B1B2B3B4B5</td><td>B1B2B3B4B5</td></tr>
<tr><th>C</th><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td></tr>
<tr><th>D</th><td>D1D2D3D4D5</td><td>D1D2D3D4D5</td><td>D1D2D3D4D5</td><td>D1D2D3D4D5</td><td>D1D2D3D4D5</td></tr>
<tr><th>E</th><td>E1E2E3E4E5</td><td>E1E2E3E4E5</td><td>E3</td><td>E1E2E3E4E5</td><td>E1E2E3E4E5</td></tr>
</tbody>
</table>
<p>Cada vez que consigamos averiguar el símbolo de una casilla, lo tachamos (borramos) de todas las demás casillas de esa fila:
</p>
<table border="1" style="text-align:center">
<thead><th></th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th></thead>
<tbody>
<tr><th>A</th><td>A1A2A3A5</td><td>A4</td><td>A1A2A3A5</td><td>A1A2A3A5</td><td>A1A2A3A5</td></tr>
<tr><th>B</th><td>B5</td><td>B1B2B3B4</td><td>B1B2B3B4</td><td>B1B2B3B4</td><td>B1B2B3B4</td></tr>
<tr><th>C</th><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td></tr>
<tr><th>D</th><td>D1D2D3D4D5</td><td>D1D2D3D4D5</td><td>D1D2D3D4D5</td><td>D1D2D3D4D5</td><td>D1D2D3D4D5</td></tr>
<tr><th>E</th><td>E1E2E4E5</td><td>E1E2E4E5</td><td>E3</td><td>E1E2E4E5</td><td>E1E2E4E5</td></tr>
</tbody>
</table>
<p>Ahora vamos recorriendo las pistas repetidamente. Para cada igualdad, hemos de comprobar si en las columnas donde aparece cada uno de los dos miembros también está el otro; si no es así, debemos tachar el extra. Por ejemplo, aplicando la pista 3 (A1=D2), puesto que en la 2ª columna no hay A1, hemos de quitar D2 de la misma:
</p>
<table border="1" style="text-align:center">
<thead><th></th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th></thead>
<tbody>
<tr><th>A</th><td>A1A2A3A5</td><td>A4</td><td>A1A2A3A5</td><td>A1A2A3A5</td><td>A1A2A3A5</td></tr>
<tr><th>B</th><td>B5</td><td>B1B2B3B4</td><td>B1B2B3B4</td><td>B1B2B3B4</td><td>B1B2B3B4</td></tr>
<tr><th>C</th><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td></tr>
<tr><th>D</th><td>D1D2D3D4D5</td><td>D1D3D4D5</td><td>D1D2D3D4D5</td><td>D1D2D3D4D5</td><td>D1D2D3D4D5</td></tr>
<tr><th>E</th><td>E1E2E4E5</td><td>E1E2E4E5</td><td>E3</td><td>E1E2E4E5</td><td>E1E2E4E5</td></tr>
</tbody>
</table>
<p>Repetimos con las pistas 4, 5, 6, 7, 8 y 10:
</p>
<table border="1" style="text-align:center">
<thead><th></th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th></thead>
<tbody>
<tr><th>A</th><td>A1A3A5</td><td>A4</td><td>A1A2A3</td><td>A1A2A3A5</td><td>A1A2A3A5</td></tr>
<tr><th>B</th><td>B5</td><td>B2B3B4</td><td>B1B3B4</td><td>B1B2B3B4</td><td>B1B2B3B4</td></tr>
<tr><th>C</th><td>C1C2C3C4</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td></tr>
<tr><th>D</th><td>D2D3D4D5</td><td>D1D3D4D5</td><td>D1D2D3D5</td><td>D1D2D3D4D5</td><td>D1D2D3D4D5</td></tr>
<tr><th>E</th><td>E1E2E5</td><td>E2E4E5</td><td>E3</td><td>E1E2E4E5</td><td>E1E2E4E5</td></tr>
</tbody>
</table>
<p>Al examinar la pista 13, observamos que en la columna 2 no hay D2, por lo tanto en la columna 1 no puede haber C3. Al aplicar la pista 14, podemos tachar A3 y A5 de la columna 1. Como se queda solo A1 en la primera fila, lo tachamos de las demás celdas de esa fila (a partir de ahora lo haremos sin avisar). De acuerdo con la pista 15, A3 no puede estar a la derecha del todo, así que lo tachamos también. Después de todos esos cambios, queda esto:
</p>
<table border="1" style="text-align:center">
<thead><th></th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th></thead>
<tbody>
<tr><th>A</th><td>A1</td><td>A4</td><td>A2A3</td><td>A2A3A5</td><td>A2A5</td></tr>
<tr><th>B</th><td>B5</td><td>B2B3B4</td><td>B1B3B4</td><td>B1B2B3B4</td><td>B1B2B3B4</td></tr>
<tr><th>C</th><td>C1C2C4</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td></tr>
<tr><th>D</th><td>D2D3D4D5</td><td>D1D3D4D5</td><td>D1D2D3D5</td><td>D1D2D3D4D5</td><td>D1D2D3D4D5</td></tr>
<tr><th>E</th><td>E1E2E5</td><td>E2E4E5</td><td>E3</td><td>E1E2E4E5</td><td>E1E2E4E5</td></tr>
</tbody>
</table>
<p>Cuando al comprobar una de las igualdades, uno de los miembros está solo, el otro debe estarlo también. Aplicando ese principio a la pista 3 en este punto, hay que dejar solo D2 en la columna 1:
</p>
<table border="1" style="text-align:center">
<thead><th></th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th></thead>
<tbody>
<tr><th>A</th><td>A1</td><td>A4</td><td>A2A3</td><td>A2A3A5</td><td>A2A5</td></tr>
<tr><th>B</th><td>B5</td><td>B2B3B4</td><td>B1B3B4</td><td>B1B2B3B4</td><td>B1B2B3B4</td></tr>
<tr><th>C</th><td>C1C2C4</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td></tr>
<tr><th>D</th><td>D2</td><td>D1D3D4D5</td><td>D1D3D5</td><td>D1D3D4D5</td><td>D1D3D4D5</td></tr>
<tr><th>E</th><td>E1E2E5</td><td>E2E4E5</td><td>E3</td><td>E1E2E4E5</td><td>E1E2E4E5</td></tr>
</tbody>
</table>
<p>Aplicamos las pistas 5 y 10, quitando E1 y E5, respectivamente, de la columna 1. Ya sabemos quién bebe E2=agua:
</p>
<table border="1" style="text-align:center">
<thead><th></th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th></thead>
<tbody>
<tr><th>A</th><td>A1</td><td>A4</td><td>A2A3</td><td>A2A3A5</td><td>A2A5</td></tr>
<tr><th>B</th><td>B5</td><td>B2B3B4</td><td>B1B3B4</td><td>B1B2B3B4</td><td>B1B2B3B4</td></tr>
<tr><th>C</th><td>C1C2C4</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td><td>C1C2C3C4C5</td></tr>
<tr><th>D</th><td>D2</td><td>D1D3D4D5</td><td>D1D3D5</td><td>D1D3D4D5</td><td>D1D3D4D5</td></tr>
<tr><th>E</th><td>E2</td><td>E4E5</td><td>E3</td><td>E1E4E5</td><td>E1E4E5</td></tr>
</tbody>
</table>
<p>Es B5, el noruego. Aplicamos las pistas 13, 8, 9 y ya no podemos seguir por este método:
</p>
<table border="1" style="text-align:center">
<thead><th></th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th></thead>
<tbody>
<tr><th>A</th><td>A1</td><td>A4</td><td>A2A3</td><td>A2A3A5</td><td>A2A5</td></tr>
<tr><th>B</th><td>B5</td><td>B2B3</td><td>B1B3B4</td><td>B1B2B3B4</td><td>B1B2B3B4</td></tr>
<tr><th>C</th><td>C1C2</td><td>C3</td><td>C1C2C4C5</td><td>C1C2C4C5</td><td>C1C2C4C5</td></tr>
<tr><th>D</th><td>D2</td><td>D1D4D5</td><td>D1D3D5</td><td>D1D3D4D5</td><td>D1D3D4D5</td></tr>
<tr><th>E</th><td>E2</td><td>E4E5</td><td>E3</td><td>E1E4E5</td><td>E1E4E5</td></tr>
</tbody>
</table>
<p>Vamos a hacer un ensayo para falsar una hipótesis. Supongamos que en la columna 2 va E5 y no E4. Entonces, por la pista 10, tiene que ir D4 ahí. Por la pista 7, no puede ir B3 en esa columna, luego tiene que ser B2; pero ahora esto se contradice con la pista 6, que dice que donde va B2 tiene que ir E4. La conclusión es que en la columna 2 no puede ir E5. Lo tachamos y continuamos, aplicando las pistas 6, 10 y 7:
<table border="1" style="text-align:center">
<thead><th></th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th></thead>
<tbody>
<tr><th>A</th><td>A1</td><td>A4</td><td>A2A3</td><td>A2A3A5</td><td>A2A5</td></tr>
<tr><th>B</th><td>B5</td><td>B2</td><td>B1B3B4</td><td>B1B3B4</td><td>B1B3B4</td></tr>
<tr><th>C</th><td>C1C2</td><td>C3</td><td>C1C2C4C5</td><td>C1C2C4C5</td><td>C1C2C4C5</td></tr>
<tr><th>D</th><td>D2</td><td>D5</td><td>D1D3</td><td>D1D3D4</td><td>D1D3D4</td></tr>
<tr><th>E</th><td>E2</td><td>E4</td><td>E3</td><td>E1E5</td><td>E1E5</td></tr>
</tbody>
</table>
<p>Nuevo punto muerto. Este es más complicado. Vamos a ensayar primero poniendo E1 en la columna 4 y viendo si nos conduce a una contradicción. Aplicamos las pistas 5, 10, 9, 15, 4, 8 y llegamos a este resultado provisional:
</p>
<table border="1" style="text-align:center">
<thead><th></th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th></thead>
<tbody>
<tr><th>A</th><td>A1</td><td>A4</td><td>A3</td><td>A5</td><td>A2</td></tr>
<tr><th>B</th><td>B5</td><td>B2</td><td>B3B4</td><td>B3B4</td><td>B1</td></tr>
<tr><th>C</th><td>C1C2</td><td>C3</td><td>C1C2C4C5</td><td>C1C2C4C5</td><td>C1C2</td></tr>
<tr><th>D</th><td>D2</td><td>D5</td><td>D1D3</td><td>D1D3</td><td>D4</td></tr>
<tr><th>E</th><td>E2</td><td>E4</td><td>E3</td><td>E1</td><td>E5</td></tr>
</tbody>
</table>
<p>Ahora, si ponemos B3 en la columna 3, por la pista 7 tiene que ir D1 en la misma columna, luego en la columna 4 tiene que ir D3, y por la pista 9 va C4 en la misma. Pero esto se contradice con la pista 8, ya que en la columna 4 sólo puede ir B4. Y si ponemos B3 en la columna 4 en vez de la 3, tenemos exactamente el mismo problema. Pongamos donde pongamos B3, llegamos a una contradicción. La única conclusión posible es que E1 no puede ir en la columna 4.
</p>
<p>Antes de seguir, una nota. En este punto se puede recurrir a una técnica del Sudoku que consiste en lo siguiente. Las columnas 1 y 5 tienen ambas C1 y C2, aunque no sabemos en qué orden. Esto quiere decir que o la 1 tiene C1 y la 5 C2, o viceversa, no hay otra posibilidad. En cualquiera de los dos casos, podemos tachar C1 y C2 de las columnas 3 y 4. Después de hacerlo, podemos recurrir a la pista 12 para asignar C1 a la columna 1, con lo que en la columna 5 iría C2. Esta es la solución parcial errónea a la que aludía al plantear el problema: C2 está en la columna correcta, como veremos, pero B1 no es la respuesta.
</p>
<p>Volvemos al punto muerto anterior, ponemos E5 en la columna 4 y continuamos. Aplicamos las pistas 10, 5, 14, 4, 7, 9, 8, 12 y obtenemos la solución:
</p>
<table border="1" style="text-align:center">
<thead><th></th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th></thead>
<tbody>
<tr><th>A</th><td>A1</td><td>A4</td><td>A2</td><td>A3</td><td>A5</td></tr>
<tr><th>B</th><td>B5</td><td>B2</td><td>B1</td><td>B4</td><td>B3</td></tr>
<tr><th>C</th><td>C1</td><td>C3</td><td>C4</td><td>C5</td><td>C2</td></tr>
<tr><th>D</th><td>D2</td><td>D5</td><td>D3</td><td>D4</td><td>D1</td></tr>
<tr><th>E</th><td>E2</td><td>E4</td><td>E3</td><td>E5</td><td>E1</td></tr>
</tbody>
</table>
<p>O sea, el dueño de C2 (la cebra) es B3 (el japonés).
</p>pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com0tag:blogger.com,1999:blog-7525171767194694125.post-57836419525935615072010-02-19T09:39:00.000+01:002013-04-24T19:47:50.586+02:00¿Quién es el dueño de la cebra?<p>Cuando era niño, vi en una revista un pasatiempo que me resultó muy curioso. Lo copié y lo repartí a mis compañeros y hubo cierto revuelo con el mismo. Se ha divulgado en otras formas, pero aún permanecen en mi memoria las frases del planteamiento y la solución tal y como figuraban en la revista.
</p>
<p>Decía así (disculpas por la propaganda de tabacos, que sospecho que estaba detrás de esa publicación, pero he preferido dejar el texto original tal y como lo conocí):</p>
<ol>
<li>Hay cinco casas de distinto color con individuos de diferentes nacionalidades. También hay distintos animales, cigarrillos y bebidas en cada una de ellas.</li>
<li>El inglés vive en la casa roja.</li>
<li>El español es el propietario del perro.</li>
<li>En la casa verde se bebe café.</li>
<li>El ucraniano bebe té.</li>
<li>La casa verde está contigua y a la derecha (la derecha del lector) de la casa de marfil.</li>
<li>El fumador de Winston tiene caracoles.</li>
<li>En la casa amarilla se fuma Camel.</li>
<li>En la casa del centro se bebe leche.</li>
<li>El noruego vive en la primera casa de la izquierda.</li>
<li>El hombre que fuma Chesterfield vive en la casa contigua a la del dueño del zorro.</li>
<li>En la casa contigua a aquella donde se encuentra el caballo se fuma Camel.</li>
<li>El fumador de Lucky Strike bebe naranjada.</li>
<li>El japonés fuma Philip Morris.</li>
<li>El noruego vive al lado de la casa azul.</li>
</ol>
<p>Las preguntas eran: ¿quién bebe agua? y ¿quién es el dueño de la cebra?</p>
<p>La respuesta a la primera pregunta es relativamente fácil, pues surge al poco tiempo de ir aplicando pistas. La segunda requiere llegar hasta el final, lo cual requiere bastante más esfuerzo.</p>
<p>Se puede abstraer este problema de la siguiente manera: sea un casillero de 5 filas y 5 columnas, en el que se deben colocar los símbolos A1, A2, A3, A4, A5, B1 a B5, C1 a C5, D1 a D5 y E1 a E5, cada uno en una casilla. Las letras indican la fila en la que colocarlos, pero los dígitos NO indican la columna; en este caso he escogido una permutación al azar de los números del 1 al 5 para cada fila. Para saber en qué columna colocar cada uno, necesitaremos las pistas. Usaremos la siguiente notación:
</p>
<ul>
<li>n=símbolo indica que en la columna n ha de ir el símbolo dado. Por ejemplo, 1=A4 indica que el símbolo A4 se debe colocar en la fila A, columna 1.</li>
<li>símbolo1=símbolo2 indica que ambos están en la misma columna.</li>
<li>símbolo1|símbolo2 indica que están en columnas adyacentes.</li>
<li>símbolo1<símbolo2 indica que símbolo1 está a la izquierda de símbolo2 (no necesariamente adyacentes).</li>
</ul>
<p>Las pistas equivalentes para este problema (no en el orden original) son:
</p>
<ol>
<li>1=B5</li>
<li>3=E3</li>
<li>A1=D2</li>
<li>A2=B1</li>
<li>A5=E1</li>
<li>B2=E4</li>
<li>B3=D1</li>
<li>B4=C5</li>
<li>C4=D3</li>
<li>D4=E5</li>
<li>A4|B5</li>
<li>C1|D5</li>
<li>C3|D2</li>
<li>A3|A5</li>
<li>A3<A5</li>
</ol>
<p>Y las preguntas son: ¿qué simbolo hay en la fila B en la misma columna donde está E2? ¿Qué símbolo hay en la fila B en la misma columna donde está C2? Es decir, hallar x e y tales que Bx=E2 y By=C2.
</p>
<p>Al principio iba a preguntar en qué columna van E2 y C2, pero después me lo he pensado, dado que hay una solución parcial errónea que tiene C2 en la columna correcta, pero no tiene el símbolo bueno en la fila B de dicha columna. Aquí se busca la solución correcta.
</p>
<p>Nótese que las dos últimas pistas juntas equivalen a la Nº. 6 del problema original, pero he querido hacerlo más general por si revisitamos este tipo de acertijos en el futuro.
</p>
<p>La equivalencia con el problema anterior es: A1=Amarilla, A2=Roja, A3=Marfil, A4=Azul, A5=Verde; B1=Inglés, B2=Ucraniano, B3=Japonés, B4=Español, B5=Noruego; C1=Zorro, C2=Cebra, C3=Caballo, C4=Caracoles, C5=Perro; D1=Philip Morris, D2=Camel, D3=Winston, D4=Lucky Strike, D5=Chesterfield; E1=Café, E2=Agua, E3=Leche, E4=Té, E5=Naranjada.
</p>pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com3tag:blogger.com,1999:blog-7525171767194694125.post-26482073078267334482010-02-18T21:38:00.000+01:002013-04-24T19:47:49.827+02:00No intente hacer esto en su oficina<object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/S6c9n51jKnQ&hl=en_US&fs=1&"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/S6c9n51jKnQ&hl=en_US&fs=1&" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"></embed></object>pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com0tag:blogger.com,1999:blog-7525171767194694125.post-1748928165690042602010-02-17T14:17:00.001+01:002013-04-24T19:47:49.660+02:00<p>SignPost es otro nuevo juego en la colección de puzles de Simon Tatham. Partiendo de la casilla 1, hay que avanzar un número indeterminado de casillas en cada paso en la dirección de la flecha, hasta acabar en la que tiene una estrella tras haber recorrido el resto del tablero visitando cada casilla una sola vez. A veces se nos indica en qué paso hay que tocar cierta casilla. Durante la resolución, lo normal es que vayamos conectando partes separadas hasta que tengamos todo conectado.
</p>
<p>Como de costumbre, genera problemas con solución única.
</p>
<p>No me ha enganchado, pero me ha entretenido, que también es importante.
</p>pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com0tag:blogger.com,1999:blog-7525171767194694125.post-9757067316363427202010-02-11T12:20:00.000+01:002013-04-24T19:47:51.256+02:00Paintfuck<p>Ya hablamos en el artículo <a href="/2009/12/lenguajes-esotericos.html">Lenguajes esotéricos</a> sobre <i>Brainfuck</i>, un lenguaje de ocho instrucciones sumamente popular. Tanto, que existen multitud de variantes de dicho lenguaje. He aquí unos cuantos:
</p>
<ul>
<li><p>En <b>COW</b> se representan la mayoría de instrucciones mediante cambios en las mayúsculas de la palabra «Moo». Es una variante de <i>Brainfuck</i> con cinco instrucciones nuevas.</p></li>
<li><p><b>FukYorBrane</b> es un lenguaje diseñado para que dos programas en <i>Brainfuck</i> compitan entre sí.</p></li>
<li><p><b>Brainfork</b> añade una instrucción para convertir el <i>Brainfuck</i> en multitarea.</p></li>
<li><p><b>Boolfuck</b> y <b>Smallfuck</b> son versiones de <i>Brainfuck</i> que operan sobre bits en vez de palabras.</p></li>
<li><p><b>Dimensifuck</b> utiliza un casillero <i>n-</i>dimensional en vez de bidimensional.
<li><p><b>Quantum brainfuck</b> opera sobre qubits en vez de sobre enteros. Está basado en la teoría de computación cuántica.</p></li>
<li><p><b>2L</b> es una variante bidimensional que usa dos símbolos (y el espacio en blanco).</p></li>
<li><p>Hay muchas más variantes; véase <a href="http://esoteric.voxelperfect.net/wiki/Category:Brainfuck_derivatives">Category: Brainfuck derivatives</a> en la <a href="http://esoteric.voxelperfect.net/wiki/">wiki de lenguajes esotéricos</a>.</p></li>
</ul>
<p>En particular, el <i>Smallfuck</i> opera sobre un casillero donde cada celda es de un bit. Tiene cinco instrucciones: elimina la entrada y la salida y, como no le hace falta incremento y decremento porque las dos operaciones hacen lo mismo, las sustituye por el comando <code>*</code>, que invierte el bit en la celda actual. Es relativamente sencillo convertir un programa en <i>Brainfuck</i> a <i>Smallfuck</i> (salvo por la entrada/salida), sustituyendo el incremento y decremento por subrutinas que manipulan los bits en bloques de, por ejemplo, 8 bits.
</p>
<p>Y del <i>Smallfuck</i> surge el <b>Paintfuck</b>. Es una variante creada por Wouter Visser que utiliza un casillero bidimensional en vez de lineal, lo cual en principio no parece nuevo. Lo que lo hace original es que está pensado para que el casillero sea representado en la pantalla, viendo en tiempo real y de forma animada cómo el programa actúa sobre el mismo.
</p>
<p>Las instrucciones <code><</code> y <code>></code> son sustituidas por <code>n</code>, <code>s</code>, <code>e</code>, <code>w</code> para los cuatro puntos cardinales respectivos. Las demás instrucciones son idénticas a las de <i>Smallfuck</i>, así que consta de siete instrucciones en total. Todo carácter que no sea una instrucción válida (incluyendo las versiones en mayúsculas de los comandos) se considera un comentario y por tanto no interviene en el programa, pero tampoco causa error. Por ello, es común que los comentarios intercalados se escriban en mayúsculas, para evitar escribir accidentalmente alguna de las letras <i>n</i>, <i>s</i>, <i>e</i> o <i>w</i> que influirían en el comportamiento del programa. El espacio de datos está inicialmente vacío (todo ceros).
</p>
<p>Aunque es un lenguaje Turing-completo cuando se aplica a casilleros infinitos en al menos una dimensión, lo cierto es que los casilleros toroidales finitos suelen ser más interesantes. Así, este sencillo programa rellena todo el casillero de unos, aunque no para nunca:
</p>
<pre class="code">
*[e[s]*]
</pre>
<p>¿Qué hace este programa? Primero, cambia la casilla inicial de 0 a 1. Esto le permite entrar en el bucle. Ya dentro del bucle, se mueve hacia el este y, si da con una casilla que no es cero, se mueve hacia el sur hasta que encuentre la primera que es cero. Si se parte de un casillero vacío, eso debería ocurrir en la línea siguiente, salvo que llegue al punto inicial, en cuyo caso se quedará enganchado indefinidamente en ese bucle. Tanto si va al sur como si no, pone a 1 la casilla actual (era 0 seguro, o de lo contrario no habría salido del bucle anterior) y repite.
</p>
<p>Programar en <i>Paintfuck</i> tiene el aliciente de que vemos cómo se desarrolla nuestro programa. Además, trabajar con bits es más simple en general. Entre los programas actualmente escritos en <i>Paintfuck</i> hay, por ejemplo, uno para hacer funcionar el autómata del <i>Juego de la Vida</i> de Conway del que hablamos en la entrada sobre <a href="/2010/01/automatas-celulares.html">autómatas celulares</a>; también hay otro autómata finito llamado <i>la hormiga de Langton</i> y un contador decimal.
</p>
<p>El contador decimal, escrito por un servidor de ustedes, representa cada número dentro de un bloque de 3×5, y se basa en una sugerencia dada en el canal de IRC #esoteric sobre realizar tal contador analizando cada dígito para ver de cuál se trata e incrementarlo. Requirió bastante esfuerzo diseñar la fuente, hallar los píxels distintivos únicos de cada número y analizar los cambios que convertían cada dígito en el siguiente. El programa final es este:
</p>
<pre class="code">
PAINTFUCK PROGRAM BY PEDRO GIMENO 2008-12-01.
swwww*es*ww*s*ee*s*ww*sseeee*wwnw*ww
*[*
nnee[*ww*s*nee]ww[*ee*ww]s
*[*ne[*w*n*se]w[*e*w]n
[*ee*e*s*w*w*s*e*e*s*ww*wnn*n]
s*[*nnee*s*w*es*s*s*e*ww*wnn]*s]
n*[*
nee[*ww*s*nee]ww[*ee*ww]s[*nne*ees*w*w*ess*w*w*n]
s*[*
nnnee[*ww*s*nee]ww[*ee*ww]s*[*nee*e*s*s*ss*w*w*wn*nn]
ss*[*
seee[*www*n*seee]www[*eee*www]n[*e*ee*s*www*n]
s*[*
ee[*ww*n*see]ww[*ee*ww]n*[*nnne*ee*sww*sss*e*en*wwws*n]
s*[*
nnne[*w*s*ne]w[*e*w]s*[*nnee*sw*s*ee*ss*w*w*wn*n]
s*[*
nnne[*w*s*ne]w[*e*w]s[*ne*sss*s*wnn*n]
s*[*
nneee[*www*s*neee]www[*eee*www]s[*ne*ees*ww*s*see*sw*w*wnn*n]
s*[*
se[*w*n*se]w[*e*w]n[*eee*ssww*n*w*n]
s*[*
ne*e*ws*s*wwwwws*nn]n]]s]s]]n]]n]
sss*[
[*e*]*wwwww]n
*]
</pre>
<p>Bueno, en realidad esa es la versión no comentada. La versión comentada es bastante más larga <a href="http://www.formauri.es/personal/pgimeno/temp/esoteric/paintfuck/decimal-counter.pfk">[1]</a>.
</p>
<p>Pueden verse este y otros programas en acción gracias al <a href="http://www.formauri.es/personal/pgimeno/temp/esoteric/paintfuck/paintfuck.php">intérprete de Paintfuck en JavaScript</a>, que también puede servir para quien quiera escribir sus propios programas en <i>Paintfuck</i>.
</p>
<h4>Referencias</h4>
<p class="noindent">[1] <a href="http://www.formauri.es/personal/pgimeno/temp/esoteric/paintfuck/decimal-counter.pfk">http://www.formauri.es/personal/pgimeno/temp/esoteric/paintfuck/decimal-counter.pfk</a>
</p>pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com0tag:blogger.com,1999:blog-7525171767194694125.post-73513077684415623732010-02-03T17:49:00.008+01:002013-04-24T19:47:50.921+02:00Dibujando curvas de Bézier cúbicas<p>He actualizado el <a href="/2010/01/aplicando-efectos-de-forma-radial.html">tutorial de GIMP sobre la aplicación de efectos de forma radial</a> para incorporarle una animación que cuando publiqué la entrada no había puesto a punto, aunque pensaba que sería una buena adición para explicar cómo actúa el filtro Coord. polares de GIMP. La animación en cuestión es esta:
</p>
<div class="center">
<img alt="Animación mostrando el efecto del filtro Coord. polares" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgq6mRV9s_uSAEQuCf1s2k9sQPTt1YC4zbcO6UUc_xxk3mlgPqCj75F3KWwrnJKUXlkiDpdIWhxwyKu11z2ynti-Yf-tXiz8zRKmAf9DAjmbXimtU4ZEyVnmgdcEv-X5gHaG_630GwX1uU/s800/pol-to-rect.gif" />
</div>
<p>Está hecha con un programa que escribí en mi lenguaje favorito para hacer pequeñas utilidades cuya velocidad no es crucial, es decir, en PHP. Cuando me puse a pensarlo, me di cuenta de que para hacer la animación que tenía en mente me iban a ser muy útiles las curvas de Bézier cúbicas. Sin embargo, al mirar el listado de funciones de la librería GD, descubrí que no contaba con funciones de dibujado de estas curvas.
</p>
<p>Tenía, pues, unas pocas opciones. Una era buscar algún otro módulo PHP capaz de dibujarlas. Resulta que el <a href="http://es.php.net/manual/en/function.imagickdraw-bezier.php">módulo de ImageMagick</a> lo hacía. Pero usar GD tenía sus ventajas para mí, porque va muy al grano y se puede trabajar con imágenes indexadas (es decir, con paleta), y el ImagickDraw no estaba muy bien documentado. Otra era buscar si alguien más había implementado lo mismo. Encontré unas pocas implementaciones, sí, una de ellas de PEAR que parecía que precisaba mucho aprendizaje para usarla, y otra que resultó tener una calidad muy pobre. La tercera opción era hacerme yo una función para dibujarlas. Finalmente me decanté por esa, aprovechando la ocasión para acometer un reto que tenía pendiente desde hace años. Con algún sistema simple me habría bastado, pero quería resolver el problema de forma definitiva para tener una función que dibujara curvas aplicable a cualquier campo, y que me sirviera de referencia en caso de necesitar reescribirla en otro lenguaje.
</p>
<h4>Métodos iterativos</h4>
<p>Así que estuve investigando los métodos que pude encontrar, para hacerme una idea de por dónde atacar el problema. Sabía de antemano que los métodos recursivos daban mejor resultado que el método iterativo simple.
<h5>El método iterativo simple</h5>
<p>La forma más sencilla de dibujar curvas de Bézier es utilizar la fórmula directamente, escogiendo un incremento de <i class="math">t</i> adecuado que nos permita recorrer el intervalo [0, 1] de forma uniforme, trazando líneas rectas entre cada punto y el anterior. Además, podemos calcular sólo los coeficientes hasta la mitad usando la igualdad <span class="math">B(P<sub>0</sub>, P<sub>1</sub>, P<sub>2</sub>, P<sub>3</sub>, <i>t</i>) = B(P<sub>3</sub>, P<sub>2</sub>, P<sub>1</sub>, P<sub>0</sub>, 1 - <i>t</i>)</span>. Ya conocía este sistema. El problema principal es que no existe un incremento de <i class="math">t</i> adecuado. Si dibujamos demasiados segmentos, podemos encontrarnos con que la línea se engrosa en algunas zonas de forma muy antiestética. Si no dibujamos suficientes, quedarán a la vista los trazos angulosos de los segmentos rectos con que se dibuja la curva. Lo malo es que en ciertas curvas es fácil que se vean ambos defectos a la vez: trazos angulosos y líneas engrosadas. Así que hay que buscar alternativas.
</p>
<h5>El método iterativo variable</h5>
<p>El problema del método iterativo simple radica en que la distancia entre puntos consecutivos (al aplicar un incremento de <i class="math">t</i> constante) no es uniforme. Este problema se podría salvar recurriendo a la fórmula de la <em>velocidad</em> de la curva, que nos da un indicador de cuán grande será el desplazamiento para un <i class="math">t</i> dado. Para una curva que va de P<sub>0</sub> a P<sub>3</sub> con puntos de control P<sub>1</sub> y P<sub>2</sub>, la fórmula es:
</p>
<div style="margin-left:4em" class="math">V(t) = 3·(1 - <i>t</i>)²·(P<sub>1</sub> - P<sub>0</sub>) + 6·<i>t</i> ·(1 - <i>t</i>)·(P<sub>2</sub> - P<sub>1</sub>) + 3·<i>t</i>²·(P<sub>3</sub> - P<sub>2</sub>)
</div>
<p>No he probado este método. A primera vista, parece que tiene el problema de que, si los incrementos no son lo suficientemente pequeños, podrían verse los polígonos de nuevo, así que habría que hacerlos lo bastante pequeños. Hacerlos de un píxel puede presentar problemas cuando los trazos son diagonales. Quizá haciéndolos de 2<sup>½</sup> (1,414213...) píxels, que es la distancia entre dos píxels diagonales, o ligeramente superior para evitar problemas de redondeos, fuera suficiente. Tendría que experimentar. Otra pega es que no veo inmediatamente cómo trasladar velocidad a píxels.
</p>
<h4>Métodos recursivos</h4>
<p>En general, los métodos recursivos se basan en subdividir la curva en múltiples segmentos, utilizando el algoritmo de De Casteljau: Sea <span class="math">B(P<sub>0</sub>, P<sub>1</sub>, P<sub>2</sub>, P<sub>3</sub>)</span> una curva de Bézier definida por los puntos <span class="math">P<sub>0</sub>, P<sub>1</sub>, P<sub>2</sub>, P<sub>3</sub></span>. Esta curva se puede dividir de forma exacta en dos segmentos, <span class="math">B<sub>0</sub>(P<sub>0</sub>, P<sub>01</sub>, P<sub>012</sub>, P<sub>0123</sub>)</span> y <span class="math">B<sub>1</sub>(P<sub>0123</sub>, P<sub>123</sub>, P<sub>23</sub>, P<sub>3</sub>)</span>. El punto <span class="math">P<sub>0</sub></span> y el punto <span class="math">P<sub>3</sub></span> coinciden con los de la curva original, ya que son los extremos del segmento curvo y lógicamente tienen que estar cada uno en una de las curva subdivididas.<ins class="ins" datetime="2011-03-23T13:33:00Z" title="Actualización 2011-03-23"> El punto de subdivisión, P<sub>0123</sub>, <em>también</em> forma parte de la curva, pues esa es precisamente la ventaja del método de De Casteljau.</ins>
</p>
<p><span class="math">P<sub>01</sub></span> es un punto situado en la recta entre <span class="math">P<sub>0</sub></span> y <span class="math">P<sub>1</sub></span>, a distancia relativa <i class="math">t</i>. Es decir, <span class="math">P<sub>01</sub> = (1 - <i>t</i>)·P<sub>0</sub> + <i>t</i> ·P<sub>1</sub></span>. Análogamente obtenemos <span class="math">P<sub>23</sub> = (1 - <i>t</i>)·P<sub>2</sub> + <i>t</i> ·P<sub>3</sub></span>. Para obtener los puntos que faltan es preciso calcular un punto extra, <span class="math">P<sub>12</sub> = (1 - <i>t</i>)·P<sub>1</sub> + <i>t</i> ·P<sub>2</sub></span>. Ahora, <span class="math">P<sub>012</sub> = (1 - <i>t</i>)·P<sub>01</sub> + <i>t</i> ·P<sub>12</sub></span> y <span class="math">P<sub>123</sub> = (1 - <i>t</i>)·P<sub>12</sub> + <i>t</i> ·P<sub>23</sub></span>. Por último, <span class="math">P<sub>0123</sub>=(1 - <i>t</i>)·P<sub>012</sub> + <i>t</i> ·P<sub>123</sub></span>. La figura siguiente ilustra el proceso. Cuando <span class="math"><i>t</i> = ½</span>, nos basta con calcular medias entre los puntos.
</p>
<div class="center">
<div><img alt="Subdivisión de una curva de Bézier cúbica en dos segmentos" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJMbycq14xcNNKFznAaoJjjf7qBSycg6VBy7f8XEkpG3g98AmjJaNBs01xZil0WrBEiCgshIU3FJ1wsz6OOB385bLPwHV_qbGBC42xjvBjUgnp-XM2Yydez8-uYVvK2tCncPHSIeaKdI4/s800/decasteljau-explicacion.png" /></div>
<div class="caption">Método de subdivisión de una curva de Bézier cúbica en dos, en el punto <span class="math"><i>t</i>=0,3</span>. La curva <span class="math">B(P<sub>0</sub>, P<sub>1</sub>, P<sub>2</sub>, P<sub>3</sub>)</span> puede ser dividida en las dos curvas <span class="math">B(P<sub>0</sub>, P<sub>01</sub>, P<sub>012</sub>, P<sub>0123</sub>)</span> y <span class="math">B(P<sub>0123</sub>, P<sub>123</sub>, P<sub>23</sub>, P<sub>3</sub>)</span>.</div>
</div>
<p>Los algoritmos recursivos realizan esta subdivisión, normalmente en el punto <span class="math"><i>t</i>=½</span>, y continúan haciendo lo mismo recursivamente con cada uno de los subsegmentos obtenidos. Difieren en la elección del caso base de la recursión.
</p>
<h5>Método de la colinealidad</h5>
<p>Las subdivisiones recursivas van aproximando cada vez más el segmento dividido a un segmento de recta. Si conseguimos determinar en qué momento es tan parecido a una recta que no es distinguible a simple vista, podremos trazar una recta en su lugar y obtener así una muy buena aproximación.
</p>
<p>La curva es más plana cuanto más próximos estén los dos puntos de control a la recta que une los puntos origen y final, así que el cálculo de esa distancia es una buena forma de determinar si son casi colineales. Para evitar una raíz, se puede calcular el cuadrado de la distancia en lugar de la distancia misma. La fórmula del cuadrado de la distancia es:
</p>
<div style="margin-left:4em" class="math"> d(P<sub>1</sub>, <span style="text-decoration:overline">P<sub>0</sub>P<sub>3</sub></span>)² = ((P<sub>x3</sub>-P<sub>x0</sub>)·(P<sub>y1</sub>-P<sub>y0</sub>) - (P<sub>y3</sub>-P<sub>y0</sub>)·(P<sub>x1</sub>-P<sub>x0</sub>))²/((P<sub>x3</sub>-P<sub>x0</sub>)² + (P<sub>y3</sub>-P<sub>y0</sub>)²)
</div>
<p class="noindent">y lo mismo cambiando <span class="math">P<sub>1</sub></span> por <span class="math">P<sub>2</sub></span>.
</p>
<p><ins class="ins" datetime="2011-03-23T13:33:00Z" title="Actualización 2011-03-23"><strong>Actualización 2011-03-23:</strong> Tengo pendiente examinar otra forma de evaluar el «grado de colinealidad», que se me ha ocurrido al releer esta entrada. La idea sería utilizar la ecuación de la recta que pasa por <span class="math">P<sub>0</sub></span> y <span class="math">P<sub>3</sub></span>, rotándola 90° si el valor absoluto de la pendiente es > 1, para hallar el valor de <i class="math">y</i> que se correspondería con el valor de <i class="math">x</i> del punto a comprobar, hallando entonces la diferencia con la <i class="math">y</i> de dicho punto. Esto podría ser más rápido que el cálculo de la distancia aquí nombrado y probablemente sería igualmente efectivo.</ins></p>
<p>Que sean casi colineales no es condición suficiente para poder aproximar la curva como un segmento de <span class="math">P<sub>0</sub></span> a <span class="math">P<sub>3</sub></span>. Hay otra condición adicional, y es que tanto <span class="math">P<sub>1</sub></span> como <span class="math">P<sub>2</sub></span> estén comprendidos entre <span class="math">P<sub>0</sub></span> y <span class="math">P<sub>3</sub></span>. Si no se impone esta condición, tanto <span class="math">P<sub>1</sub></span> como <span class="math">P<sub>2</sub></span> pueden estar fuera del segmento <span class="math" style="text-decoration:overline">P<sub>0</sub>P<sub>3</sub></span> y el resultado teórico sería una curva que desde uno de los extremos va temporalmente en dirección contraria al otro extremo, y ese fragmento no sería dibujado. Por ejemplo, si consideramos la curva <span class="math">B((0,0), (-3,0), (10,0), (10,0))</span>, los puntos <span class="math">P<sub>1</sub></span> y <span class="math">P<sub>2</sub></span> son colineales con <span class="math" style="text-decoration:overline">P<sub>0</sub>P<sub>3</sub></span> y, pese a ello, la curva se extiende un poco hacia el lado de abscisas negativas.
</p>
<p>En casos como el indicado, la subdivisión podría requerir infinitos pasos para el subsegmento que contiene el pico. Para evitarlo, se puede dar un margen de error: si <span class="math">P<sub>2</sub></span> y <span class="math">P<sub>3</sub></span> están ambos dentro de un<del class="del" datetime="2011-03-23T13:33:00Z" title="Actualización 2011-03-23"> cuadrado</del><ins class="ins" datetime="2011-03-23T13:33:00Z" title="Actualización 2011-03-23"> rectángulo</ins> de tamaño <span class="math">(|P<sub>x3</sub>-P<sub>x0</sub>|+ε, |P<sub>y3</sub>-P<sub>y0</sub>|+&epsilon)</span>, se consideran buenos.
</p>
<p>Este es el método que escogí finalmente. Los resultados son muy decentes y el algoritmo es aceptablemente rápido. Como parámetros, la distancia al cuadrado que puse para considerar los puntos colineales es 0,125 píxels y el ε que escogí es 0,25 (un cuarto de píxel).
</p>
<h5>Método de la longitud</h5>
<p>La longitud de una curva de Bézier puede ser calculada numéricamente, no hay una fórmula explícita general para la misma. Además de mediante una integral que requiere métodos numéricos clásicos y complicados, hay una forma recursiva de aproximarla que, además, nos da un método para dibujarla.
</p>
<p>Sea <span class="math">L<sub>0</sub></span> la longitud del segmento <span class="math" style="text-decoration:overline">P<sub>0</sub>P<sub>3</sub></span> y L<span class="math"><sub>1</sub></span> la suma de las longitudes de los segmentos restantes, concretamente <span class="math">L<sub>1</sub>=|<span style="text-decoration:overline">P<sub>0</sub>P<sub>1</sub></span>| + |<span style="text-decoration:overline">P<sub>1</sub>P<sub>2</sub></span>| + |<span style="text-decoration:overline">P<sub>2</sub>P<sub>3</sub></span>|</span>. Entonces,<ins datetime="2011-03-23T13:33:00Z" class="ins" title="Actualización 2011-03-23"> la media de esos valores,</ins> <span class="math">(L<sub>1</sub> + L<sub>0</sub>) / 2</span>, da una aproximación a la longitud, y <span class="math">L<sub>1</sub> - L<sub>0</sub></span> da una medida del error. Dicho error decrece en relación <span class="math">2<sup>-4m</sup></span> donde m es el número de subdivisiones <a href="http://steve.hollasch.net/cgindex/curves/cbezarclen.html">[1]</a>.
</p>
<p>Se puede entonces dibujar el segmento de forma que si la longitud del subsegmento actual es lo bastante pequeña, se trace dicho subsegmento como recta.
</p>
<p>No he probado este método. En realidad es muy similar al anterior, ya que cuando el error es pequeño, significa que los puntos están próximos a ser colineales, lo cual es casi lo mismo que el método anterior. En este no nos salvamos de calcular al menos cuatro raíces cuadradas, por lo cual lo deseché antes de probarlo.
</p>
<h4>Otros métodos</h4>
<h5>Descomposición en cuadráticas</h5>
<p>En <a href="http://www.timotheegroleau.com/Flash/articles/cubic_bezier_in_flash.htm">[2]</a> se da una forma de descomponer una curva cúbica en cuatro segmentos de curva cuadrática. No me ha convencido el resultado, pero al menos he creído oportuno dejar una referencia para quien quiera averiguar más. Con este método, queda abierto el problema de cómo dibujar una curva cuadrática de forma eficiente.
</p>
<h5>Partición de la derivada por octantes</h5>
<p>Para terminar, vamos a considerar un método que ha sido utilizado por <a href="http://es.wikipedia.org/wiki/METAFONT">METAFONT</a>, el programa de diseño tipográfico de D. E. Knuth. Para leer la documentación del programa METAFONT<ins class="ins" datetime="2011-03-23T13:33:00Z" title="Actualización 2011-03-23"> decentemente</ins>, necesitaremos la utilidad Weave, capaz de extraer archivos escritos en TeX (el formato de proceso de texto de Knuth) a partir de archivos WEB (que contienen una mezcla del programa en Pascal y la documentación en TeX). El original está aquí: <a href="http://mirror.ctan.org/systems/knuth/dist/mf/mf.web">http://mirror.ctan.org/systems/knuth/dist/mf/mf.web</a>.<ins class="ins" datetime="2011-03-23T13:33:00Z" title="Actualización 2011-03-23"> La explicación, junto con el código correspondiente, comienza en la §384.</ins>
</p>
<p>La idea del método es similar a la de los círculos de Bresenham: se clasifica la derivada de la curva por octantes y se transforma cada uno de los octantes al primero, de manera que la pendiente (el incremento de <i class="math">y</i> por cada incremento unitario de <i class="math">x</i>) no sea superior a la unidad. Con este método se consigue que cada incremento de <i class="math">x</i> vaya asociado a cero o un incremento de <i class="math">y</i>, con lo cual la curva resultante tiene una fineza superior. El precio a pagar por tanto refinamiento es un programa que cuesta mucho entender, además de que cuesta bastantes cálculos. El beneficio es la curva más perfecta posible, igual que la línea de Bresenham es la recta más perfecta posible.
</p>
<h4>Referencias</h4>
<p class="noindent">[1] <a href="http://steve.hollasch.net/cgindex/curves/cbezarclen.html">http://steve.hollasch.net/cgindex/curves/cbezarclen.html</a>
<br />[2] <a href="http://www.timotheegroleau.com/Flash/articles/cubic_bezier_in_flash.htm">http://www.timotheegroleau.com/Flash/articles/cubic_bezier_in_flash.htm</a>
</p>pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com0tag:blogger.com,1999:blog-7525171767194694125.post-47102172877174243192010-01-30T16:59:00.001+01:002013-04-24T19:47:50.294+02:00Aplicando efectos de forma radial<p>Hay un filtro para GIMP que puede dar buen resultado aplicado a logos u otros propósitos espectaculares, pero cuya utilidad se ve limitada por la forma en que es aplicado. Estoy hablando de <a href="http://docs.gimp.org/es/plug-in-wind.html"><em>Viento</em></a>.
</p>
<p>El filtro <i>Viento</i> sólo se puede aplicar horizontalmente, hacia la izquierda o hacia la derecha. Esto limita mucho su campo de aplicación, pese a que si pudiera aplicarse de forma radial desde un punto de la imagen hacia los bordes, se podrían conseguir efectos interesantes.
</p>
<p>Aquí vamos a ver cómo esquivar esa limitación y aplicar el filtro radialmente. Para ello nos ayudaremos de otro filtro presente en ese mismo menú: <a href="http://docs.gimp.org/es/plug-in-polar-coords.html">Coord. polares</a>.
</p>
<p>Hace nada compilé un paquete de Gimp 2.6.8 para Debian Lenny, así que es la versión que vamos a usar en este tutorial. En este caso apenas hay diferencias respecto a la 2.4, pero trataré de explicar las que recuerde.
</p>
<p>Primero, dejemos claro el objetivo que perseguimos mediante un par de ejemplos:
</p>
<div class="center" style="border: dashed 1px #866; padding: 2ex">
<div><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxl9pbLv6M_c7pzaxQZnwKJ6U7SGoYIIwoZVPXECZQvXGElBcsSP-cMdmigP8WYu-zUE6a-pQ2xBVuNRM6ewOqv1LoLpfpYuU1naEskcfZ-usUyyDuq_QtGTNsJa5cUA2cQ3omk6_6VLU/s1600/Glowing-hot.jpg"><img alt="Logotipo creado con Archivo/Crear/Logotipos/Calor resplandeciente" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxl9pbLv6M_c7pzaxQZnwKJ6U7SGoYIIwoZVPXECZQvXGElBcsSP-cMdmigP8WYu-zUE6a-pQ2xBVuNRM6ewOqv1LoLpfpYuU1naEskcfZ-usUyyDuq_QtGTNsJa5cUA2cQ3omk6_6VLU/s400/Glowing-hot.jpg" /></a></div>
<div class="caption">Logotipo creado con <kbd>Archivo > Crear > Logotipos > Calor resplandeciente...</kbd> (en Gimp 2.4 los logotipos están en el menú Xtns de la ventana de herramientas).</div>
<div style="margin-top: 2ex"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaCs0llW7Q1Gu8bl2JR_obGu-K0A18xp8LPMLWh7l0x4-qkZTQmsrxgIY8bs1WRhpBJi8IfO_L1a22fItknwQm_W74KIDb-MnMlXuptZ1369c-SQBjtx3HGwLr0XB35v7HvcAzat2GAC0/s1600-h/Glowing-hot-finished.jpg"><img alt="Logotipo con el efecto Viento aplicado de forma radial" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaCs0llW7Q1Gu8bl2JR_obGu-K0A18xp8LPMLWh7l0x4-qkZTQmsrxgIY8bs1WRhpBJi8IfO_L1a22fItknwQm_W74KIDb-MnMlXuptZ1369c-SQBjtx3HGwLr0XB35v7HvcAzat2GAC0/s400/Glowing-hot-finished.jpg" /></a></div>
<div class="caption">Resultado de aplicar el efecto Viento de forma radial</div>
</div>
<div class="center" style="margin-top: 2ex; border: dashed 1px #866; padding: 2ex">
<div><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRbQ_PkqWtjpyKMZROb0zey5qpdRiVgxIgIemTh0IEaqFaAcYAuPqHjEH337qyLArcTKwDE2fUeFR4JDNn6lPHowY24w0JTYpbfv9HedF7ADMSAXl3w2gbByKuJi3CurVCeVr_ng1sX9w/s1600/Peluso.jpg"><img alt="Peluso" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRbQ_PkqWtjpyKMZROb0zey5qpdRiVgxIgIemTh0IEaqFaAcYAuPqHjEH337qyLArcTKwDE2fUeFR4JDNn6lPHowY24w0JTYpbfv9HedF7ADMSAXl3w2gbByKuJi3CurVCeVr_ng1sX9w/s400/Peluso.jpg" /></a></div>
<div class="caption">Peluso</div>
<div style="margin-top: 2ex"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhsB60nYOqc8Gn3Yc4HD-qJ_cAd2YvZGOq5gEkxQLeb_WiEUUHw0z5mpusvC62V8trk_b3xVM_VbpJOl3XlZ19BPgA_STDxowR18JpKy7NRfERbscbcdym6fMbEzwQs7gta4pAHs7PGFs/s1600-h/Peluso-finished.jpg"><img alt="Peluso con efecto viento radial" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhsB60nYOqc8Gn3Yc4HD-qJ_cAd2YvZGOq5gEkxQLeb_WiEUUHw0z5mpusvC62V8trk_b3xVM_VbpJOl3XlZ19BPgA_STDxowR18JpKy7NRfERbscbcdym6fMbEzwQs7gta4pAHs7PGFs/s400/Peluso-finished.jpg" /></a></div>
<div class="caption">Peluso con efecto viento radial</div>
</div>
<p>En el caso del logotipo, para que aparezca el texto bien perfilado he aplicado el efecto solamente a la capa central, ya que la superpuesta ayuda a perfilar; he hecho la prueba de aplanar primero la imagen para aplicar el efecto a todo, pero se emborronaba mucho.
</p>
<p>Para ver si el efecto queda aceptable o no, a modo de <i>quickie</i>, bastará con aplicar los pasos cuarto, quinto, sexto, séptimo y noveno (transformación, giro, aplicar efecto, deshacer giro, deshacer transformación), que son los que comprenden la esencia del método. Nos quedará un círculo en pequeño en el centro, pero con suerte bastará para que podamos decidir si vale la pena intentar el método completo o no.
</p>
<h4>Paso cero: ¿Tiene nuestra capa canal alfa? ¿Lo necesita? ¿Hay que ajustar el color de fondo? ¿Hay que duplicar la capa?</h4>
<p>Este tutorial puede aplicarse tanto a capas con canal alfa, como a capas sin él. Al final del proceso, se nos puede haber contaminado la imagen con píxels que no pertenecen a ella, que serán transparentes si hay canal alfa, o del color del fondo si no lo hay. Si tiene alfa, después se puede colocar la imagen original debajo para disimular, pero si se trata de un color plano, basta con asignárselo al color de fondo y nos ahorraremos algún paso. Así que, si no tiene canal alfa, decidamos primero si lo añadimos o no y, en caso de que no, ajustemos el color de fondo. Por ejemplo, si es una capa que contiene un logo en blanco sobre fondo verde homogéneo, se puede ajustar el color de fondo a ese verde y trabajar sin canal alfa. Si lo vamos a hacer con una fotografía, como la de Peluso, lo que mejor resultado proporcione será probablemente duplicar la capa y añadir alfa si no tiene, de manera que la imagen original quede debajo, para que así las partes contaminadas por transparencia dejen a la vista dicha imagen. En cualquier caso, es una buena idea tener una copia de la capa original como referencia, para facilitar el último paso de recorte.
</p>
<h4>Primer paso: Recentrar</h4>
<p>Nuestro primer paso será escoger cuál va a ser el punto central del que van a salir todos los «rayos». En el caso de Peluso, he escogido el punto (284, 276). El filtro Coord. polares toma como centro el de la capa con la que trabaja, así que vamos a ampliar el tamaño de manera que dichos puntos queden en el centro. Si queremos que el centro coincida con el de la imagen, este paso no será necesario. En el caso del logo, he optado por ello y me lo he saltado.
</p>
<p>La forma de conseguir el nuevo centro es: averiguar las distancias desde el centro escogido (en coordenadas relativas a la capa) hasta ambos lados de la capa, averiguar la mayor de las dos, y agrandar la capa al doble de dicha distancia, recolocándola de forma que nuestro centro elegido caiga en el centro de la versión agrandada. Esto se hace tanto para la coordenada horizontal como para la vertical. Aquí, «agrandar» no se refiere a escalar, sino a cambiar el tamaño extendiendo el borde. Es la operación que en el Gimp se llama «Tamaño del borde de capa...».
</p>
<p>La imagen de Peluso tiene 640×480 píxels. La distancia del centro elegido al borde izquierdo es 284; al borde derecho, es 640-284=356. Por tanto, tenemos que expandir la capa por su lado izquierdo dejando una imagen de 356·2=712 píxels de ancho. Repetimos lo mismo con las coordenadas verticales: la distancia al borde superior es 276 y al borde inferior es 480-276=204 píxels, por lo tanto está más cerca del inferior, luego habrá que expandirlo por debajo hasta que la imagen llegue a 276·2=552 píxels de alto. Es decir, tras redimensionar la capa, ésta ha de quedar pegada a la esquina superior derecha. Así que vamos a <kbd>Capa > Tamaño del borde de capa</kbd>, quitamos la cadenita que liga el tamaño vertical y el horizontal e introducimos en Anchura, 712, y en Altura, 552 (píxels, claro). En Desplazamiento, como queremos llevarla a la esquina superior derecha, subimos X al máximo, que es 72, e Y la dejamos a 0. Pulsamos Redimensionar. Ahora el centro de la capa ya es el que queremos.
</p>
<h4>Segundo paso: Agrandar</h4>
<p>Dada la manera en que vamos a usar el filtro Coord. polares, vamos a tropezar con el problema de que la imagen final va a ser más pequeña que la inicial. Concretamente, con forma de un círculo cuyo diámetro es la menor de las dimensiones de la capa. Para compensar este efecto, hemos de agrandarla de antemano de forma que la parte original quede inscrita dentro de dicho círculo.
</p>
<div class="center">
<div><img alt="Resultado de aplicar el filtro Coord. polares y su inverso. Arriba, sin agrandar la capa primero; abajo, agrandándola." src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBnRwIuSrwIKM7f67_NACWTKn5X4dcsOQt4oQHATEzfH5C26Y5Y2_Oeap3COQiz-FjZbdMQsvf25R-Rjest0li4itfJzRxGh9vIsST7Xn4apayouJj_maXZD3Zt1lJRpxTL7SRRxUAVIA/s800/Inscribe-circunscribe.png" /></div>
<div class="caption">Resultado de aplicar el filtro Coord. polares y su inverso. Arriba, sin agrandar la capa primero; abajo, agrandándola. Obsérvese que en la de abajo la imagen original queda completa.</div>
</div>
<p>Este tamaño extra se aplica incluso si ya la hemos agrandado en el primer paso, y <em>después</em> de hacerlo; así el botón Centrar hará lo que esperamos.
</p>
<p>Para saber el radio del círculo dentro del cual estará nuestra capa inscrita, hemos de echar mano del teorema de Pitágoras. Primero, hay que hallar el centro; esto lo hacemos dividiendo los tamaños horizontal y vertical por 2. Con la imagen de Peluso, el horizontal es 356 y el vertical, 276. El punto más alejado de dicho centro es la esquina (todas ellas), así que hay que calcular la distancia a la misma. Aplicamos Pitágoras: (356²+276²)<sup>½</sup> ~= 450,45. Al final del proceso, debido a errores de precisión acumulados se colarán unos pocos píxels indeseados; para compensarlo, añadimos un 1% (el error máximo que he observado no llega a 0,4% pero más vale estar seguros, ya que va a ser una diferencia pequeña). Redondeamos por lo alto para estar más seguros aún. Buscaremos, pues, un círculo de 455 píxels de radio, o sea, 455·2=910 píxels de diámetro, así que agrandamos la capa para que sea un cuadrado capaz de circunscribir dicho círculo, es decir, le damos un tamaño de 910×910. Para ello, vamos de nuevo a <kbd>Capa > Tamaño del borde de capa</kbd>, quitamos la cadenita, introducimos Anchura = 910, Altura = 910 y pulsamos el botón Centrar para que nos centre el contenido actual. Si hemos escogido un centro distinto, podemos ver el resultado un poco descentrado al pulsar el botón; eso es normal y no debe preocuparnos, pero nuestro centro sí debe estar en el centro del cuadrado resultante; si no es así, quizá hemos colocado la capa en el cuadrante incorrecto en el paso anterior y hay que volver atrás. Si todo ha ido bien, pulsamos Redimensionar.
</p>
<p>Para el logo, de 526×238, los valores son (263²+119²)<sup>½</sup> ~= 288,67, que multiplicado por 2,02 (el doble más el 1%) y redondeado al alza nos da que la capa debe ser de 584×584.
</p>
<h4>Tercer paso: Extender los bordes</h4>
<p>La capa del logo con la que vamos a trabajar no llega hasta los bordes, así que si se contamina un borde con transparencia a causa de los errores de precisión, no pasa nada; en ese caso podemos pasar al paso siguiente. El caso de Peluso es distinto, porque la fotografía sí que llega hasta los bordes. Si no nos preocupa que se haga un poco transparente en los bordes, podemos saltar este paso. Si estamos aplicando el truco descrito en el Paso Cero, dejando una copia de la imagen en la capa inferior, no lo necesitaremos. Si el borde queda mal incluso al usar esa estratagema, podemos probar con el truco aquí descrito.
</p>
<p>La intención es extender cada borde a su correspondiente espacio vacío. No conozco ninguna forma que no sea tediosa. La idea es seleccionar la fila de píxels superior (una selección de Ancho×1), copiarla y pegarla en el espacio vacío superior, escalándola sólo verticalmente y moviéndola para cubrir la totalidad del mismo. Para ver lo que hacemos, es preferible haber hecho primero <kbd>Imagen > Ajustar lienzo a las capas</kbd>. Cuando hayamos terminado, hacemos lo mismo con la fila de píxels inferior. Después, con la fila de píxels izquierda (una selección de 1×Alto) y por último, con la derecha. Al hacer la izquierda y la derecha, cogeremos también lo que ya hemos extendido al hacer la parte de arriba y la de abajo.
</p>
<p>Por ejemplo, así queda Peluso tras este paso:
</p>
<div class="center">
<div><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgqYphBVFoWU1gGRiKx0Fac-jRffa72g4XvZua2FRy-9Qoh8mF7VLSCt8Zzsq8O4d0RYC8u_ZnaYRfS6eD8UJi7Ir9pRqI8Oh-cU-SbM7dyydVQpuW1BUvz_R9j-lKDBVITq3hiN7Xsx8/s1600-h/Peluso-extendido.jpg"><img alt="Peluso con los bordes extendidos" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgqYphBVFoWU1gGRiKx0Fac-jRffa72g4XvZua2FRy-9Qoh8mF7VLSCt8Zzsq8O4d0RYC8u_ZnaYRfS6eD8UJi7Ir9pRqI8Oh-cU-SbM7dyydVQpuW1BUvz_R9j-lKDBVITq3hiN7Xsx8/s400/Peluso-extendido.jpg" /></a></div>
<div class="caption">Peluso con los bordes extendidos de la forma recién descrita.</div>
</div>
<h4>Cuarto paso: Polares a Cartesianas(*)</h4>
<p>Vamos al meollo del asunto. Abrimos el filtro Coord. polares y desmarcamos la casilla «A polares», dejando todo lo demás por defecto (profundidad circular 100%, ángulo de desfase 0, no mapear al revés, sí mapear desde arriba). Obtenemos una versión deformada de la imagen de partida. La transformación interpreta la imagen original como si estuviera en coordenadas polares y representa en el eje X, el ángulo, y en el Y, el radio relativo a la menor de las dos dimensiones de la imagen(*). Es decir, todos los efectos que apliquemos verticalmente se harán sobre el radio. Mmmm, eso se parece a lo que queremos.
</p>
<div class="center">
<div><img alt="Animación que reproduce el efecto de aplicar el filtro Polares a Cartesianas" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgq6mRV9s_uSAEQuCf1s2k9sQPTt1YC4zbcO6UUc_xxk3mlgPqCj75F3KWwrnJKUXlkiDpdIWhxwyKu11z2ynti-Yf-tXiz8zRKmAf9DAjmbXimtU4ZEyVnmgdcEv-X5gHaG_630GwX1uU/s800/pol-to-rect.gif" /></div>
<div class="caption"><strong>Actualización 2-2-2010.</strong> Esta animación muestra qué transformación realiza el filtro Coord. Polares al aplicarlo a una imagen habiendo marcado las casillas «Mapear al revés» y «Mapear desde arriba» y con la casilla «A polares» desactivada.</div>
</div>
<h4>Quinto paso: Rotación de 90° a la izquierda</h4>
<p>Pero el efecto <i>Viento</i> no opera en dirección vertical, sino únicamente horizontal, así que antes de aplicarlo, primero rotamos la imagen 90° hacia la izquierda con <kbd>Capa > Transformar > Rotar 90° en sentido antihorario</kbd>.
</p>
<h4>Sexto paso: Aplicar el efecto</h4>
<p>Ahora es cuando aplicamos el filtro <i>Viento</i>. Hemos de jugar con los ajustes hasta que consigamos lo que buscamos. En el caso de Peluso, he usado Borde Trasero, Umbral 12 y Fuerza 30, dejando Estilo y Dirección intactos. Con el logo, he usado Borde Delantero, Umbral 10 y Fuerza 100.
</p>
<h4>Séptimo paso: Rotación de 90° a la derecha</h4>
<p>Para poder devolver la imagen a su estado inicial con el efecto aplicado, tenemos que deshacer las transformaciones que hemos acumulado. Primero, la giramos a la derecha 90° con <kbd>Capa > Transformar > Rotar 90° en sentido horario</kbd>.
</p>
<h4>Octavo paso: Ampliar (Antialias primera parte)</h4>
<p>El filtro <i>Viento</i> produce unas líneas muy finas, de un píxel de ancho, lo cual causa problemas de <i>aliasing</i> al convertir la imagen de nuevo a polares(*), que sería el paso siguiente si no fuera por ese problema. Según la imagen, podemos tener suerte o no, y no he encontrado otra forma de averiguarlo que el ensayo y error. Con Peluso no lo he necesitado; con el logo, sí.
</p>
<p>Para conseguir un <i>antialiasing</i> que palie esa pega, el truco que empleamos es ampliar la imagen en este momento, de tal forma que las líneas que se convertirán de cartesianas a polares(*) sean más gruesas, para que después, al reducir con Sinc una vez convertido, sea la propia interpolación la que consiga el <i>antialias</i>. Así que, si hemos tenido en un intento anterior problemas de <i>aliasing</i>, ahora es el momento de ampliar la capa usando <kbd>Capa > Escalar capa</kbd>. Recomiendo un valor que no sea un múltiplo exacto como 200%, porque lo he probado y ha sido un fracaso; en cambio, usando un 121% de ampliación he tenido éxito.
</p>
<p>Aquí se ve la diferencia sin <i>antialias</i> y con él:
</p>
<div class="center">
<div><img alt="Sin antialias" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-ARbkkDMdQ5R5xkvhxl28rtQim9RcUlmpgx7i3o4IIVHDTvGSD-y7zI1bPqeezITOzeDvCY9roabGoIL730dwdj30aH-HhaB5IGh5-uETbkNU5OlIG-fxgwBGEoCYnhuUrjFyJqSj4SY/s1600/Glowing-hot-aliasing-demo.jpg" /> <img alt="Con antialias" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2BFdmSmZBgyH0R4L04NK5PrclTpvBc4_rShtSbRnqK0Lx4LNYWV7q01SgM02SFixGNZRmB6YgL1kJjt1DTzh15ZvGVey1scbIaetbRYBwo7KaPKf_rN0bVceo8AzwJ4Wt4dclnsfjbEY/s1600/Glowing-hot-antialiasing-demo.jpg" /></div>
<div class="caption">Comparación entre no usar antialias y usarlo</div>
</div>
<h4>Noveno paso: Cartesianas a Polares(*)</h4>
<p>Abrimos de nuevo Coord. polares y marcamos la casilla «A polares» sin tocar nada más. En la previsualización, veremos ya un anticipo del resultado final. Aceptamos.
</p>
<div class="center">
<div><img alt="Animación que reproduce el efecto de aplicar el filtro Polares a Cartesianas" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsTGRLvyNiLqS2WmbRsd0Tldru4Ktd6PWsaFQAPLm0e4dRigQ9oiHBrDiBji2PeuMVXFRFsz22gr4wmmkH2HR7eoHe4FOS831dppW-EO063RoRXHVHthht8MtYWEUTtpMMuXnbhtTjVH8/s800/rect-to-pol.gif" /></div>
<div class="caption"><strong>Actualización 2-2-2010.</strong> Esta animación muestra qué transformación realiza el filtro Coord. Polares al aplicarlo a una imagen habiendo marcado las casillas «Mapear al revés» y «Mapear desde arriba» y con la casilla «A polares» activada.</div>
</div>
<h4>Décimo paso: Reducción (Antialias segunda parte)</h4>
<p>Ahora hay que deshacer la ampliación realizada en el octavo paso, si la hemos hecho. Para ello, podemos usar como tamaño destino el tamaño de la imagen en píxels obtenido en el segundo paso (910×910 para Peluso).
</p>
<h4>Undécimo y último paso: Recortar</h4>
<p>Ya solo falta recortar la capa. Esto puede ser complicado si no tenemos una capa de referencia con el tamaño y la posición originales. Si contamos con ella, podemos seleccionarla, usar <kbd>Capa > Transparencia > Alfa a selección</kbd>, y luego elegir la capa procesada y usar <kbd>Capa > Recortar a la selección</kbd>. Esta última opción no recuerdo que estuviera en Gimp 2.4, así que a quien lo tenga puede que le toque hacer el recorte artesanalmente.
</p>
<p class="noindent" style="font-size: 90%">(*): <i>Yo diría que la caja de selección «A polares» está al revés: que cuando está desmarcada es cuando convierte a coordenadas polares y viceversa, representando en un eje el ángulo y en el otro el radio. Por tanto, la primera transformación sería de cartesianas a polares y la transformación inversa convertiría de polares a cartesianas, y no al revés como indica la casilla. Agradecería opiniones al respecto.</i>
</p>
<h4>Variaciones</h4>
<p>El efecto Viento no es el único que se beneficia de esta técnica, así que hay otras opciones para los pasos quinto a séptimo. Otro efecto que no se puede aplicar radialmente es <a href="http://docs.gimp.org/es/plug-in-shift.html"><i>Desplazamiento</i></a>, aunque en este caso tiene la opción de aplicarlo tanto horizontal como verticalmente, por lo cual si queremos probarlo podemos omitir el paso de la rotación de 90°. No he encontrado aún una utilidad al resultado. Otro posible uso es el <a href="http://docs.gimp.org/es/plug-in-gauss.html"><i>Desenfoque gaussiano</i></a>, ajustando el radio X a cero. Más efectos curiosos: <a href="http://docs.gimp.org/es/plug-in-pixelize.html"><i>Pixelizar</i></a>, <a href="http://docs.gimp.org/es/plug-in-glasstile.html"><i>Mosaico de cristal</i></a>, <a href="http://docs.gimp.org/es/plug-in-ripple.html"><i>Ondular</i></a>, <a href="http://docs.gimp.org/es/script-fu-erase-rows.html"><i>Borrar las otras filas</i></a>... Lo que se le ocurra a cada uno.
</p>
<h4>Nota final</h4>
<p>Después de escribir este tutorial, me he dado cuenta de que alguien ya había escrito uno similar: <a href="http://www.gimpusers.com/tutorials/rays-of-light-behind-text.html">http://www.gimpusers.com/tutorials/rays-of-light-behind-text.html</a>. Sin embargo, he preferido publicarlo porque proporciona valor añadido al explicar cómo recentrar y cómo ampliar la capa en caso de que no sea un logo sino otro tipo de imagen, y explica un método de antialiasing más sofisticado que el que sugiere el autor de ese otro tutorial. El hecho de que ambos tutoriales consten de once pasos es mera coincidencia.
</p>pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com0tag:blogger.com,1999:blog-7525171767194694125.post-11889333797238512752010-01-28T12:10:00.015+01:002013-04-24T19:47:49.492+02:00Solución al problema de la probabilidad de cada longitud<p><a href="/2010/01/solution-to-probability-of-lengths.html">(English version available)</a>
</p>
<p>En la <a href="/2010/01/subida-automatica-de-archivos-gmail.html">entrada sobre IMAP</a> se preguntaba por la probabilidad de cada posible longitud de la cadena resultante en el generador de palabas al azar. El bucle tenía esta forma:
</p>
<pre class="php">
<i>for (</i>$i <i>=</i> 0<i>;</i> $i <i><</i> 7 <i>+</i> mt_rand<i>(</i>0<i>,</i> 4<i>);</i> $i<i>++)</i> <em>/* añadir un carácter */</em> <i>;</i>
</pre>
<p>La clave está en que el número extraído que sirve como condición de final de bucle puede variar en cada iteración. Es diferente de un bucle similar que fijara el número de antemano, porque entra en juego la intersección de sucesos. Veamos por qué y cómo.
</p>
<p>En el momento en que <code>$i</code> alcanza el valor 7, si el valor de <code>mt_rand</code> es 0 entonces el bucle termina; si es cualquier otro valor, continúa. Es evidente que la probabilidad de que la longitud sea 7 es 1/5 = 20%.
</p>
<p>Cuando el valor de <code>$i</code> es 8, hace falta que <code>mt_rand</code> devuelva 0 ó 1 para que el bucle termine, lo cual ocurrirá dos de cada cinco veces. Sin embargo, para que la longitud alcance el valor 8 es condición que primero <code>mt_rand</code> haya devuelto un valor entre 1 y 4, lo cual ocurrirá 4 de cada 5 veces. No son sucesos independientes. Por tanto, la probabilidad combinada es 4/5 · 2/5 = 8/25 = 32%.
</p>
<p>Para que <code>$i</code> llegue a 9, se tienen que dar a la vez que <code>mt_rand</code> devuelva un valor entre 1 y 4 la primera vez, y un valor entre 2 y 4 la segunda vez. Además, el bucle parará cuando <code>mt_rand</code> esté entre 0 y 2, lo cual ocurrirá 3 de cada 5 veces. Combinando las probabilidades, tenemos P(longitud 9) = 4/5 · 3/5 · 3/5 = 36/125 = 28,8%.
</p>
<p>Con el mismo razonamiento, la probabilidad de que llegue a 10 es 4/5 · 3/5 · 2/5 · 4/5 = 96/625 = 15,36%.
</p>
<p>La que queda podemos calcularla bien siguiendo el mismo razonamiento, ahora que le hemos cogido carrerilla, o bien restando de 1 todas las anteriores: 4/5 · 3/5 · 2/5 · 1/5 · 5/5 = 24/625 = 3,84% = 100% - 20% - 32% - 28,8% - 15,36%.
</p>
<p>Resumiendo:
</p>
<ul>
<li>P(longitud 7) = 20%</li>
<li>P(longitud 8) = 32%</li>
<li>P(longitud 9) = 28,8%</li>
<li>P(longitud 10) = 15,36%</li>
<li>P(longitud 11) = 3,84%</li>
</ul>
<p>O sea, será 8 casi una de cada tres veces, seguido de cerca por 9, luego 7, luego 10, y unas pocas veces llegará a longitud 11. Como siempre he dicho, la probabilidad no es intuitiva.
</p>
<p>Este programa ayuda a verificar la corrección de los números:
</p>
<pre class="php"><?php
$MAX <i>=</i> 10000000<i>;</i>
$arr <i>= array(</i>0<i>,</i> 0<i>,</i> 0<i>,</i> 0<i>,</i> 0<i>);
for (</i>$n <i>=</i> 0<i>;</i> $n <i><</i> $MAX<i>;</i> $n<i>++)
{
for (</i>$i <i>=</i> 0<i>;</i> $i <i><</i> mt_rand<i>(</i>0<i>,</i> 4<i>);</i> $i<i>++)
;</i>
$arr<i>[</i>$i<i>]++;
}</i>
$l <i>=</i> strlen<i>(</i>$MAX<i>);
for (</i>$i <i>=</i> 0<i>;</i> $i <i><</i> 5<i>;</i> $i<i>++)
{</i>
printf<i>(</i><b>"%2d -> %{$l}d/$MAX = %f%%\n"</b><i>,</i> $i<i>+</i>7<i>,</i> $arr<i>[</i>$i<i>],</i> $arr<i>[</i>$i<i>]/</i>$MAX<i>*</i>100<i>);
}</i>
?>
</pre>
<p>Aquí el resultado de una ejecución de muestra:
</p>
<pre class="code"> 7 -> 2000626/10000000 = 20.006260%
8 -> 3198181/10000000 = 31.981810%
9 -> 2879481/10000000 = 28.794810%
10 -> 1538148/10000000 = 15.381480%
11 -> 383564/10000000 = 3.835640%
</pre>pgimenohttp://www.blogger.com/profile/00144934861814699933noreply@blogger.com0