Du betrachtest gerade 12) Spiel-Statistik

12) Spiel-Statistik

  • Beitrags-Autor:Peter Kühn
  • Beitrags-Kategorie:chess_analyser
  • Beitrags-Kommentare:Ein Kommentar

Im vorherigen Kapitel hatten wir abgeleitete Kennzahlen auf der Ebene von einzelnen Schach-Positionen und Zügen definiert, diese berechnet und in der Datenbank-Tabelle da_position gespeichert.

ACPL

Darauf aufbauend werden wir nun wie angekündigt weitere Statistiken erstellen, immer bezogen auf eine ganze Schachpartie, und beginnen dabei mit dem ACPL-Wert, welcher die durchschnittliche Abweichung in Centipawn-Einheiten vom jeweils besten Zug berechnet. Kleine ACPL-Werte legen nahe, dass vorwiegend gute Züge nahe am Optimum von der entsprechenden Seite gespielt wurden.

Wir werden die ACPL-Werte für beide Seiten berechnen, da wir auch daran interessiert sind, wie gut die Gegenspieler in den betreffenden Partien agierten.

Betrachten wir dazu eine zufällige Partie aus unserem Fundus, indem wir die einzelnen Positionen/Züge mit den zugehörigen Kennzahlen in unserer Datenbank verknüpfen. Dazu berechnen wir gleich noch die Differenz zwischen der Centipawn-Bewertung für den besten Zug (aus der vorherigen Zeile) und dem Centipawn-Wert für den real ausgeführten Zug.

select
	t.fen,	t.half_move_num ,	t.move_white,	t.move_black,	t.best_move_uci,	t.centipawn,	t.centipawn_prv,	(t.centipawn_prv - t.centipawn) cp_diff,	t.accuracy
from
	(
	select
		p.fen,
		p.half_move_num ,
		p.move_white ,
		p.move_black ,
		pa.best_move_uci ,
		pa.centipawn ,
		lag(pa.centipawn) over (
		order by p.game_id,
		p.half_move_num ) as centipawn_prv,
		dp.accuracy
	from
		chess.position p
	join chess.position_analysis as pa on
		p.id = pa.position_id
	left outer join chess.da_position as dp on
		dp.position_id = pa.position_id
	where
		p.game_id = 2124959
	order by
		p.game_id,
		p.half_move_num ) t;

In der Spalte „cp_diff“ unten im Bild finden sich die Ausgangswerte zur Berechnung der ACPL-Kennzahlen.

die Vorstufe zur ACPL-Berechnung

Im Bild oben haben wir der Übersichtlichkeit halber der centipawn-Bewertung der betreffenden Zeile bzw. Schachposition (in Spalte fen) die Centipawn-Bewertung der Ausgangsposition (aus der Zeile darüber) beigeordnet (Spalte centipawn_prv). Die Werte in der Spalte cp_diff ergeben sich nun einfach aus der Differenz centipawn_prv – centipawn.

Sehen wir uns die Werte von cp_diff genauer an. Zunächst stellen wir fest, dass in der allerersten Zeile der Wert dafür fehlt. Die Ursache liegt auf der Hand – für die erste Zeile fehlt der Vorgängerwert und damit die Berechnungsgrundlage. Das ist ok.

In der zweiten Zeile zog Weiß den suboptimalen Zug Sb1-c3, wodurch die centipawn-Bewertung von -13 (also einem marginalen Vorteil von Schwarz) auf -92 (fast einen ganzen Bauern) absackte. Dies manifestiert sich absolut in einem Verlustwert von 79 centipawn in der cp_diff-Spalte und lässt sich gleichfalls am accuracy-Wert von nur ca. 72% ablesen. Auch dieses Ergebnis erscheint plausibel.

In der dritten Zeile zog Schwarz Lb4-c3, statt des empfohlenen Dd8-e7, woraus sich eine Differenz von -14 ergibt. Dass hier eine negative Zahl entsteht, erscheint auf den ersten Blick widersinnig, da wir sie ja als Verlust in Hinblick auf den optimalen centipawn-Wert interpretieren wollen. Dabei zu berücksichtigen ist allerdings, dass die centipawn-Bewertung bewusst aus der Sicht von Weiß definiert wurde. Ein negativer Verlust bedeutet daher einen Gewinn für Weiß und das wiederum einen Wertverlust für Schwarz.

Stellung nach 9).. Lb4-c3

Wir berechnen daher eine Kennzahl cpl („centipawn loss“) aus dem Differenzwert, indem wir zusätzlich das Vorzeichen umkehren, falls Schwarz am Zug war. Warum wir stattdessen nicht einfach den Absolutwert heranziehen, sehen wir gleich.

cpl-Berechnung (1. Näherung)

Wir hätten nun erwartet, dass die Verlustwerte in der neuen Spalte cpl alle positiv oder 0 (im Falle von 100% accuracy) sind. Doch wie kann es sein, dass in der Spalte cpl Werte ungleich 0 auch dann auftauchen, wenn der vorgeschlagene optimale Schachzug tatsächlich ausgeführt wurde?!

Betrachten wir zum Beispiel gleich die vierte Zeile. Es wurde genau der vorgeschlagene Zug 9) Lb2-c3 ausgeführt und trotzdem entstand ein centipawn-Verlust von 11!

Nun, das ist zu erklären. In der Position nach 9) Lb2-c3 kann Stockfish erstens auf die vorherige Analyse aufbauen und zweitens diese nun um mindestens einen Halbzug vertiefen. Dadurch kann es zu einem abweichenden Resultat bei der Bewertung kommen. Und solange das Schachprogramm keine zwingende Variante zur Mattsetzung gefunden hat, sind alle Bewertungsergebnisse stets mit dem Makel der Unvollkommenheit behaftet!

Wie wollen wir also mit dieser Situation umgehen? Wenn wir den cpl-Wert nun umdefinieren, dass auch negative Verluste möglich wären, würde das bedeuten, dass wir Züge besser als den „optimalen“ Zug einstufen müssten. Das passte dann nicht mehr zur accuracy oder man müsste dafür Werte größer als 100% zulassen und neu berechnen.

Dadurch entstünde eine kaum noch zu durchschauende Komplexität und Intransparenz, so dass wir lieber die näherliegende und leicht verständliche Herangehensweise vorziehen, den cpl-Wert immer dann auf 0 zu setzen, wenn die accuracy 100% beträgt.

Bevor wir dies umsetzen, müssen wir eine weitere Anomalie beheben. Angenommen, Weiß kann seinen Gegner in 5 Zügen matt setzen. Definitionsgemäß haben wir die centipawn-Bewertung in dieser Situation auf den exorbitant hohen Wert 32000 gesetzt. Wenn nun Weiß stattdessen einen anderen Zug wählt, der immer noch zu einer total gewonnenen Position für ihn führt mit z.B. einer centipawn-Bewertung von 2000, dann wäre der cpl-Wert für diesen Zug 30000, was sich auf den Durchschnittswert ACPL verheerend auswirken würde. Damit wir diese fatalen Ausschläge erkennen und beheben können, betrachten wir bei der cpl-Berechnung zusätzlich die Kennzahl accuracy.
Liegt diese bei 99% oder höher, setzen wir den cpl-Wert standardmäßig auf 0.

In diesem Zusammenhang trennen wir den cpl-Wert gleich noch auf in cpl_white und cpl_black und passen unsere SQL-Abfrage entsprechend an.

select
	t.fen,
	t.half_move_num ,
	t.move_white,
	t.move_black,
	t.best_move_uci,
	t.centipawn,	
	case
		when t.half_move_num % 2 = 0 then null
		else case
			when t.accuracy >= 99 then 0
			else (t.centipawn_prv - t.centipawn) * t.inverse_sign
		end
	end cpl_white,
	case
		when t.half_move_num % 2 = 1 then null
		else case
			when t.accuracy >= 99 then 0
			else (t.centipawn_prv - t.centipawn) * t.inverse_sign
		end
	end cpl_black,
	t.accuracy
from
	(
	select
		p.fen,
		p.half_move_num ,
		case
			when p.half_move_num % 2 = 1 then 1
			else -1
		end inverse_sign,
		p.move_white ,
		p.move_black ,
		pa.best_move_uci ,
		pa.centipawn ,
		lag(pa.centipawn) over (
	order by
		p.game_id,
		p.half_move_num ) as centipawn_prv,
		dp.accuracy
	from
		chess.position p
	join chess.position_analysis as pa on
		p.id = pa.position_id
	left outer join chess.da_position as dp on
		dp.position_id = pa.position_id
	where
		p.game_id = 2124959
	order by
		p.game_id,
		p.half_move_num ) t;
finale cpl-Werte für die ACPL-Berechnung

STDCPL

ACPL („average centipawn loss“) ist wie der Name bereits ausdrückt ein Durchschnittswert. Dabei ist ein möglichst niedriger Wert zwar gut, aber allein nicht unbedingt aussagekräftig. Denn die beste Gegenwehr nutzt einem Spieler wenig, wenn er z.B. einmal einen Verlustzug ausgeführt hat, den der Gegenspieler auszunutzen versteht. Selbst wenn der Spieler von dort an jeden Zug mit einer Genauigkeit von 100% spielt, muss er die Partie verlieren. Um diesen Sachverhalt sichtbar zu machen, benutzen wir die Standardabweichung der cpl-Werte als statistisches Maß hierfür. Je kleiner dieser Wert ist, desto geringer ist die Wahrscheinlichkeit für einen groben Schnitzer.

Für die Berechnung von ACPL und der neuen Kennzahl STDCPL ( „Standard Deviation Centipawn Loss“) benutzen wir die eingebauten Funktionen AVG() und STDDEV_SAMP() von MariaDB.


	select
		avg(cpl_white) acpl_white,
		avg(cpl_black) acpl_black,
		stddev_samp(cpl_white) stdcpl_white,
		stddev_samp(cpl_black) stdcpl_black,
		avg(case when cpl_white is not null then accuracy else null end) accuracy_avg_white,
		avg(case when cpl_black is not null then accuracy else null end) accuracy_avg_black
from
		(
	select
			t.fen,
			t.half_move_num ,
			t.move_white,
			t.move_black,
			t.best_move_uci,
			t.centipawn,
			case
				when t.half_move_num % 2 = 0 then null
			else case
					when t.accuracy = 100 then 0
				else (t.centipawn_prv - t.centipawn) * t.inverse_sign
			end
		end cpl_white,
			case
				when t.half_move_num % 2 = 1 then null
			else case
					when t.accuracy = 100 then 0
				else (t.centipawn_prv - t.centipawn) * t.inverse_sign
			end
		end cpl_black,
			t.accuracy
	from
			(
		select
				p.fen,
				p.half_move_num ,
				case
					when p.half_move_num % 2 = 1 then 1
				else -1
			end inverse_sign,
				p.move_white ,
				p.move_black ,
				pa.best_move_uci ,
				pa.centipawn ,
				lag(pa.centipawn) over (
		order by
				p.game_id,
				p.half_move_num ) as centipawn_prv,
				dp.accuracy
		from
				chess.position p
		join chess.position_analysis as pa on
				p.id = pa.position_id
		left outer join chess.da_position as dp on
				dp.position_id = pa.position_id
		where
				p.game_id = 2124959
		order by
				p.game_id,
				p.half_move_num ) t ) t2;

Im obigen Match „Goliath vs David“ entsprechen die resultierenden ACPL– und STDCPL-Werte voll und ganz unseren Erwartungen.

Bobby Fischer vs. J Gloger

Wenn wir die Korrektheit unserer Berechnungen für obige Partie überprüfen wollen, indem wir sie z.B. von lichess.org analysieren lassen, erhalten wir allerdings abweichende Ergebnisse, die sich nicht durch Rechenungenauigkeiten erklären lassen.

Partie auf lichess.org
ACPL-Berechnung von lichess.org

Haben wir also falsch gerechnet? Kurze Antwort: Nein, aber wir verwenden eine andere Berechnungsgrundlage.
Wenn wir uns nochmal die obigen Daten ansehen, machen wir uns wieder klar, dass wir die bekannten Eröffnungszüge aus den Theoriebüchern bewusst gar nicht bewerten wollten!

Dieser doch gravierende Unterschied zwischen den ACPL-Werten von lichess.org und unseren (4 vs 30 bzw. 16 vs 97) kennzeichnet den überragenden Einfluss von Eröffnungswissen im moderen Schachspiel. Doch was uns im Spielervergleich wirklich interessiert, ist Schachkönnen ohne Zuhilfenahme von dicken Eröffnungstheoriebüchern und vorbereiteten Varianten, evtl. ermittelt durch lange Computer-Analysen!

ACCURACY

Es steht zu vermuten, dass auch die Genauigkeitsrate sinken wird, wenn wir bekannte Eröffnungszüge unberücksichtigt lassen. Wir rechnen das kurz nach, indem wir unser SQL etwas erweitern.

die durchschnittliche Genauigkeit beider Seiten

Wie fast nicht anders zu erwarten, sinken die Genauigkeits-Werte signifikant im Vergleich zur vollständigen Partie inklusive aller Züge aus dem Eröffnungsbuch.

Eine Anomalie

Folgende Situation vergleichbar zu dem obigen Fall ist denkbar: die Schachengine schlägt einen Zug vor mit einem errechneten centipawn-Wert x. Der Zugführende spielt aber einen alternativen Zug und die daraus resultierende Stellung wird mit einem centipawn-Wert y belohnt, wobei x < y !
Demnach korrigiert die engine nachträglich ihren eigenen Vorschlag. Tatsächlich finden wir in unseren Analysedaten Tausende solcher Fälle. Doch glücklicherweise werden auch diese Züge stets mit einer accuracy von 100% belohnt!

Stellungs-Komplexität

Wir hatten im vorigen Kapitel ein relativ aufwändiges Verfahren zur Beurteilung der Stellungskomplexität kurz vorgestellt und wegen dieses Aufwandes auch gleich wieder verworfen.

Wünschenswert wäre daher ein einfacher Indikator, mit dessen Hilfe wir beurteilen könnten, ob eine vorliegende Position kompliziert ist, wo spielbare Züge schwerer zu finden sind als in einer Position, wo der nächste Zug auf der Hand liegt bzw. sogar erzwungen ist. Dann ließe sich die Qualität von einzelnen Zügen gewichten, indem man gute Züge in einer schwierigen/einfachenPosition mit einem Bonus bzw. Malus versieht. Andersherum ließen sich schlechte Züge in schwierigen Positionen leichter verzeihen als in einfachen Positionen.

Eine naheliegender Kandidat für einen solchen Komplexitäts-Indikator wäre der Rechenaufwand, welchen Stockfish für die Stellungsanalyse erbringt. Dazu haben wir bereits die Kennzahlen nodes, depth, seldepth und time_sec für jeden Zug in der Tabelle position_analysis mitgeführt.

Zu diesem Zweck haben wir mit nodes etwas experimentiert, indem wir mit Hilfe von Erwartungswert und Standardabweichung die drei Klassen (-1, 0, 1 für „leicht“, „normal“ und „schwierig“) bildeten und einige bekannte Schachpartien daraufhin untersuchten.

 select t3.half_move_num,
 t3.move_white ,t3.move_black ,
	t3.nodes_white,
	#t3.avg_nodes_white ,
	#t3.stdev_nodes_white ,
	case when t3.nodes_white > t3.avg_nodes_white + t3.stdev_nodes_white then 1 
	     when t3.nodes_white < t3.avg_nodes_white - t3.stdev_nodes_white then -1
	     else 0
	end complexity_white,
	t3.nodes_black,
	#t3.avg_nodes_black,
	#t3.stdev_nodes_black,
	case when t3.nodes_black > t3.avg_nodes_black + t3.stdev_nodes_black then 1 
	     when t3.nodes_black < t3.avg_nodes_black - t3.stdev_nodes_black then -1
	     else 0
	end complexity_black	
 from (
 select
	t2.half_move_num,
	t2.move_white ,t2.move_black ,
	t2.nodes_white,
	avg(t2.nodes_white) over () as avg_nodes_white ,
	stddev_samp(t2.nodes_white) over () as stdev_nodes_white ,
	t2.nodes_black,
	avg(t2.nodes_black) over () as avg_nodes_black,
	stddev_samp(t2.nodes_black) over () as stdev_nodes_black
from
	(
	select
				t.half_move_num ,
				t.move_white ,t.move_black ,
				case
						when t.half_move_num % 2 = 1 and t.accuracy is not null then t.nodes
			else null
		end nodes_white,
				case
						when t.half_move_num % 2 = 0 and t.accuracy is not null then t.nodes
			else null
		end nodes_black
	from
				(
		select
					p.half_move_num ,
					p.move_white ,p.move_black ,
						pa.nodes,
						dp.accuracy 
		from
					chess.position p
		join chess.position_analysis as pa on
					p.id = pa.position_id
		left outer join chess.da_position as dp on
					dp.position_id = pa.position_id
		where
					p.game_id = 10823787 # Schach-WM 1972: Partie 13, Spasski - Fischer
	 ) t)t2)t3
order by
					t3.half_move_num;
Schach-WM 1972: Partie 13, Spasski – Fischer

Nach eingehender Prüfung des Ergebnisses müssen wir allerdings feststellen, dass kein signifikanter Zusammenhang zwischen Rechenaufwand (in nodes) und tatsächlicher Stellungskomplexität erkennbar ist. Somit verwerfen wir diesen Ansatz wieder.

Sharpness

Wie bereits ausgeführt, lässt sich anhand der WDL-Bewertung von Stockfish diese Kennzahl schwerlich ablesen, so dass wir vorerst von einer weiteren Verdichtung auf Partieebene absehen.

weitere Aggregationen

Abschließend zählen wir für jede Partie die Anzahl Halbzüge sowie diejenigen Züge von Weiß und Schwarz, die wir als ‚BLUNDER‘, ‚MISTAKE‘, ‚ENGINE‘ und ‚NORMAL‘ (für die übrigen Züge) eingestuft hatten. Wir begnügen uns vorerst mit diesen Partie-Kennzahlen und starten mit der Implementierung.

Datenmodell-Erweiterung

Wie bereits skizziert, erstellen wir für die Partie-Kennzahlen eine separate Tabelle da_game, die wir mit der Tabelle game über eine entsprechende Fremdschlüssel-Beziehung verknüpfen.

Tabelle da_game

Package-Erweiterung

Zum Befüllen unserer neuen Tabelle erweitern wir das vorhandene PL/SQL-Package in der Datenbank. Die neue Prozedur gen_da_game löscht zunächst die Zieltabelle und iteriert danach über alle analysierten Partien, um für jede einzelne einen entsprechenden Datensatz in der Tabelle da_game einzutragen.

SET SESSION SQL_MODE='ORACLE';
DELIMITER //

CREATE OR REPLACE PACKAGE da AS
  -- must be declared as public!
  PROCEDURE gen_da_position(p_player_id NUMBER(20));
  procedure gen_da_game;
END da;
//

CREATE OR REPLACE PACKAGE BODY da as

	function calc_win_percent(p_centipawns in double) return double
	as
		v_win_percent double;
	begin
		v_win_percent := 50.0 + 50.0 * (2.0 / (1.0 + exp(-0.00368208 * p_centipawns)) - 1.0);
		return v_win_percent;
	end calc_win_percent;
	
	function calc_accuracy (p_winpercent_before in double, p_winpercent_after in double) return double
	as
		v_win_diff double;
		v_accuracy double;
	begin
		if p_winpercent_after >= p_winpercent_before then
			return (100.0);
		end if;
	
		v_win_diff := p_winpercent_before - p_winpercent_after;
		v_accuracy := 103.1668 * exp(-0.04354 * (v_win_diff)) - 3.1669;
		v_accuracy := greatest(v_accuracy, 0.0);
		v_accuracy := least(v_accuracy, 100.0);
		
		return(v_accuracy);
	end calc_accuracy;
		
	function calc_judgement (p_winpercent_before in double, p_winpercent_after in double) return varchar2
	as
		v_win_diff double;
		v_judgement varchar2(100);
	begin
		if p_winpercent_after >= p_winpercent_before then
			return ('ENGINE');
		end if;
	
		v_win_diff := p_winpercent_before - p_winpercent_after;
		
		if v_win_diff >= 30.0 then
			v_judgement := 'BLUNDER';
		elsif v_win_diff >= 20.0 then
			v_judgement := 'MISTAKE';
		elsif v_win_diff >= 10.0 then
			v_judgement := 'INACCURACY';
		end if;
		
		return(v_judgement);
	end calc_judgement;
		
	function calc_sharpness (p_win in integer, p_loose in integer) return double
	as
		v_win_rescaled double;
		v_loose_rescaled double;
		v_sharpness double;
	begin	
		if p_win = 0 or p_loose = 0 then
			return null;
		end if;
	
		v_win_rescaled := p_win / 1000.0;
		v_loose_rescaled := p_loose / 1000.0;
		v_sharpness := pow(2.0 / (log(1.0 / v_win_rescaled - 1.0) + log(1.0 / v_loose_rescaled - 1.0)), 2);
	
	    return v_sharpness;
	end calc_sharpness;
	
	procedure upsert_da_position(p_da_position_rec in da_position%rowtype)
	as
	begin
		insert into da_position values (p_da_position_rec.position_id, 
										p_da_position_rec.white_winning_chances,
										p_da_position_rec.white_score_rate,
										p_da_position_rec.white_draw_rate,
										p_da_position_rec.black_winning_chances,
										p_da_position_rec.black_score_rate,
										p_da_position_rec.black_draw_rate,
										p_da_position_rec.accuracy,
										p_da_position_rec.judgement,
										p_da_position_rec.sharpness
										)
			on duplicate key 
				update white_winning_chances = p_da_position_rec.white_winning_chances,
	                   white_score_rate = p_da_position_rec.white_score_rate,
					   white_draw_rate = p_da_position_rec.white_draw_rate,
					   black_winning_chances = p_da_position_rec.black_winning_chances,
	                   black_score_rate = p_da_position_rec.black_score_rate,
	                   black_draw_rate = p_da_position_rec.black_draw_rate,
	                   accuracy = p_da_position_rec.accuracy,
	                   judgement = p_da_position_rec.judgement,
	                   sharpness = p_da_position_rec.sharpness;
	end upsert_da_position;
	
	PROCEDURE gen_da_position(p_player_id NUMBER(20))
	as
		v_total_prv, v_total, v_win_rate_prv, v_win_rate, v_white_winning_chances_prv, v_white_winning_chances, v_score double;
	    v_score_rate, v_draw_rate, v_loss_rate, v_white_score_rate, v_black_score_rate double;
		v_loss_rate_prv, v_accuracy, v_black_winning_chances_prv, v_black_winning_chances double;
		v_white_draw_rate, v_black_draw_rate double;
		v_judgement varchar2(100);
		v_da_position_rec da_position%rowtype;
	begin
		for v_rec in 
		(
		select
		lag(g.id) over (order by g.id, p.half_move_num) as game_id_prv,
		g.id as game_id, 
		pa.position_id,
		p.half_move_num,
		decode(p.move_white, '', null, p.move_white) move_white,
		decode(p.move_black, '', null, p.move_black) move_black,
		lag(pa.best_move_uci) over (order by g.id, p.half_move_num) as best_move_uci_prv,
		pa.best_move_uci,
		lag(pa.centipawn) over (order by g.id, p.half_move_num) as centipawn_prv,
		pa.centipawn,
		lag(pa.wins) over (order by g.id, p.half_move_num) as wins_prv,
		pa.wins,
		lag(pa.draws) over (order by g.id, p.half_move_num) as draws_prv,
		pa.draws,
		lag(pa.losses) over (order by g.id, p.half_move_num) as losses_prv,
		pa.losses
		from
			game g
		join player pw on
			g.white_player_id = pw.id
		join player pb on
			g.black_player_id = pb.id
		join position p on
			(g.id = p.game_id)
		join position_analysis pa on
			(p.id = pa.position_id)
		where
			pw.id = p_player_id
			or pb.id = p_player_id
	    order by g.id, p.half_move_num
		)
		loop
			if v_rec.best_move_uci_prv is null or v_rec.game_id <> v_rec.game_id_prv
			then
				continue; -- no move evaluation available
			end if;
			v_da_position_rec := null;
			v_total_prv := v_rec.wins_prv + v_rec.draws_prv + v_rec.losses_prv;
			v_win_rate_prv := v_rec.wins_prv / v_total_prv;
			v_loss_rate_prv := v_rec.losses_prv / v_total_prv;
			v_white_winning_chances_prv := calc_win_percent(v_rec.centipawn_prv);
			v_black_winning_chances_prv := 100 - v_white_winning_chances_prv;
	
			v_total := v_rec.wins + v_rec.draws + v_rec.losses;
			v_score := v_rec.wins + v_rec.draws / 2;
			v_score_rate := v_score / v_total;
			v_win_rate := v_rec.wins / v_total;
			v_draw_rate := v_rec.draws / v_total;
			v_loss_rate := v_rec.losses / v_total;
			
			v_white_winning_chances := calc_win_percent(v_rec.centipawn);
			v_black_winning_chances := 100 - v_white_winning_chances;
			-- logging.log('v_white_winning_chances: '||v_white_winning_chances);
			-- logging.log('v_black_winning_chances: '||v_black_winning_chances);
				
			if (v_rec.half_move_num MOD 2 = 0) 
			then -- black moved
				v_black_score_rate := 100 * v_score_rate;
				v_white_score_rate := 100 * (1 - v_score_rate);
				if (v_rec.move_black = v_rec.best_move_uci_prv) then
					v_accuracy := 100.0;
					v_judgement := 'ENGINE';
				else
				    v_accuracy := calc_accuracy(v_black_winning_chances_prv, v_black_winning_chances);
					v_judgement := calc_judgement(v_black_winning_chances_prv, v_black_winning_chances);
				end if;
			else -- white moved
				v_white_score_rate := 100 * v_score_rate;
				v_black_score_rate := 100 * (1 - v_score_rate);
				if (v_rec.move_white = v_rec.best_move_uci_prv) then
					v_accuracy := 100.0;
					v_judgement := 'ENGINE';
				else
				    v_accuracy := calc_accuracy(v_white_winning_chances_prv, v_white_winning_chances);
					v_judgement := calc_judgement(v_white_winning_chances_prv, v_white_winning_chances);
				end if;
			end if;
			v_white_draw_rate := 100 * v_draw_rate;
			v_black_draw_rate := 100 * v_draw_rate;

			--
			-- set record data
			--
			v_da_position_rec.position_id := v_rec.position_id;
			v_da_position_rec.white_winning_chances := v_white_winning_chances;
			v_da_position_rec.white_score_rate := v_white_score_rate;
			v_da_position_rec.white_draw_rate := v_white_draw_rate;
			v_da_position_rec.black_winning_chances := v_black_winning_chances;
			v_da_position_rec.black_score_rate := v_black_score_rate; 
			v_da_position_rec.black_draw_rate := v_black_draw_rate;
			v_da_position_rec.accuracy := v_accuracy;
			v_da_position_rec.judgement := v_judgement;
			v_da_position_rec.sharpness := calc_sharpness(v_rec.wins, v_rec.losses);
			--
			-- upsert record
			--
			upsert_da_position(v_da_position_rec);
		end loop;
		
	end gen_da_position;
	
	procedure gen_da1game (p_game_id NUMBER(20))
	as
		v_acpl_white double;
	    v_acpl_black double;
		v_stdcpl_white double;
		v_stdcpl_black double;
		v_accuracy_avg_white double;
		v_accuracy_avg_black double;
		v_sum_engine_moves_white integer;
		v_sum_engine_moves_black integer;
		v_sum_normal_moves_white integer;
		v_sum_normal_moves_black integer;
		v_sum_mistake_moves_white integer;
		v_sum_mistake_moves_black integer;
		v_sum_blunder_moves_white integer;
		v_sum_blunder_moves_black integer;
		v_game_length integer;
	begin
		select
			avg(cpl_white) acpl_white,
			avg(cpl_black) acpl_black,
			stddev_samp(cpl_white) stdcpl_white,
			stddev_samp(cpl_black) stdcpl_black,
			avg(case when cpl_white is not null then accuracy else null end) accuracy_avg_white,
			avg(case when cpl_black is not null then accuracy else null end) accuracy_avg_black,
			sum(case when cpl_white is not null and judgement = 'ENGINE' then 1 else 0 end) sum_engine_moves_white,
			sum(case when cpl_black is not null and judgement = 'ENGINE' then 1 else 0 end) sum_engine_moves_black,
			sum(case when cpl_white is not null and judgement is null then 1 else 0 end) sum_normal_moves_white,
			sum(case when cpl_black is not null and judgement is null then 1 else 0 end) sum_normal_moves_black,
			sum(case when cpl_white is not null and judgement = 'MISTAKE' then 1 else 0 end) sum_mistake_moves_white,
			sum(case when cpl_black is not null and judgement = 'MISTAKE' then 1 else 0 end) sum_mistake_moves_black,
			sum(case when cpl_white is not null and judgement = 'BLUNDER' then 1 else 0 end) sum_blunder_moves_white,
			sum(case when cpl_black is not null and judgement = 'BLUNDER' then 1 else 0 end) sum_blunder_moves_black,
			max(half_move_num) game_length
			into
			v_acpl_white,
			    v_acpl_black,
				v_stdcpl_white,
				v_stdcpl_black,
				v_accuracy_avg_white,
				v_accuracy_avg_black,
				v_sum_engine_moves_white,
				v_sum_engine_moves_black ,
				v_sum_normal_moves_white,
				v_sum_normal_moves_black,
				v_sum_mistake_moves_white,
				v_sum_mistake_moves_black,
				v_sum_blunder_moves_white,
				v_sum_blunder_moves_black,
				v_game_length
		from
				(
			select
						t.half_move_num ,
						t.move_white,
						t.move_black,
						t.best_move_uci,
						t.centipawn,
						case
							when t.half_move_num mod 2 = 0 then null
					else case
								when t.accuracy >= 99 then 0
						else (t.centipawn_prv - t.centipawn) * t.inverse_sign
					end
				end cpl_white,
						case
							when t.half_move_num mod 2 = 1 then null
					else case
								when t.accuracy >= 99 then 0
						else (t.centipawn_prv - t.centipawn) * t.inverse_sign
					end
				end cpl_black,
						t.accuracy,
							t.judgement ,
							t.sharpness
			from
						(
				select
							p.half_move_num ,
							case
								when p.half_move_num mod 2 = 1 then 1
						else -1
					end inverse_sign,
							p.move_white ,
							p.move_black ,
							pa.best_move_uci ,
							pa.centipawn ,
							lag(pa.centipawn) over (
				order by
							p.game_id,
							p.half_move_num ) as centipawn_prv,
							dp.accuracy,
							dp.judgement ,
							dp.sharpness
				from
							chess.position p
				join chess.position_analysis as pa on
							p.id = pa.position_id
				left outer join chess.da_position as dp on
							dp.position_id = pa.position_id
				where
							p.game_id = p_game_id
				order by
							p.game_id,
							p.half_move_num ) t ) t2;
		insert into
			da_game (game_id,
			acpl_white,
			acpl_black,
			stdcpl_white,
			stdcpl_black,
			accuracy_avg_white,
			accuracy_avg_black,
			sum_engine_moves_white,
			sum_engine_moves_black,
			sum_normal_moves_white,
			sum_normal_moves_black,
			sum_mistake_moves_white,
			sum_mistake_moves_black,
			sum_blunder_moves_white,
			sum_blunder_moves_black,
			game_length)
		values (p_game_id,
			v_acpl_white,
			v_acpl_black,
			v_stdcpl_white,
			v_stdcpl_black,
			v_accuracy_avg_white,
			v_accuracy_avg_black,
			v_sum_engine_moves_white,
			v_sum_engine_moves_black,
			v_sum_normal_moves_white,
			v_sum_normal_moves_black,
			v_sum_mistake_moves_white,
			v_sum_mistake_moves_black,
			v_sum_blunder_moves_white,
			v_sum_blunder_moves_black,
			v_game_length);
	end gen_da1game;
	
	procedure gen_da_game
	as
	begin
		truncate table da_game;
		for v_rec in (select distinct p.game_id from da_position dap join position p on p.id =dap.position_id order by game_id)
		loop
			gen_da1game(v_rec.game_id);
			commit;
		end loop;
		
	end gen_da_game;
end da;
//

DELIMITER ;

Die Ausführungszeit der Prozedur beträgt lediglich 13 Sekunden.

die neue Packageprozedur da.gen_da_game

Mit einer kurzen Sichtkontrolle auf die entstandenen Daten wollen wir dieses Kapitel beenden.

Partie-Statistik in da_game
Einzelsatz-Betrachtung

Im nächsten Kapitel beschäftigen wir uns mit der Visualisierung unserer gewonnenen Daten.

Dieser Beitrag hat einen Kommentar

Schreibe einen Kommentar