[Scummvm-git-logs] scummvm master -> ffb1dc3610960eb4cf7623ab8361a67395866cc4

Die4Ever 30947252+Die4Ever at users.noreply.github.com
Thu Oct 28 00:10:13 UTC 2021


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
ffb1dc3610 GROOVIE: T11H Pente AI puzzle


Commit: ffb1dc3610960eb4cf7623ab8361a67395866cc4
    https://github.com/scummvm/scummvm/commit/ffb1dc3610960eb4cf7623ab8361a67395866cc4
Author: Die4Ever (die4ever2005 at gmail.com)
Date: 2021-10-27T19:09:49-05:00

Commit Message:
GROOVIE: T11H Pente AI puzzle

Changed paths:
    engines/groovie/logic/cake.cpp
    engines/groovie/logic/pente.cpp
    engines/groovie/logic/pente.h
    engines/groovie/logic/triangle.cpp
    engines/groovie/script.cpp
    engines/groovie/script.h
    engines/groovie/video/roq.cpp
    engines/groovie/video/roq.h


diff --git a/engines/groovie/logic/cake.cpp b/engines/groovie/logic/cake.cpp
index 3bef335066..d87642bb87 100644
--- a/engines/groovie/logic/cake.cpp
+++ b/engines/groovie/logic/cake.cpp
@@ -321,6 +321,7 @@ byte CakeGame::aiGetBestMove(int search_depth) {
 
 void CakeGame::testCake() {
 	warning("starting CakeGame::testCake()");
+	uint32 oldSeed = _random.getSeed();
 	// test the draw condition, grouped by column
 	runCakeTestNoAi(/*move 1*/ "7777777" /*8*/ "6666666" /*15*/ "5555555" /*22*/ "34444444" /*30*/ "333333" /*36*/ "2222222" /*43*/ "01111111" /*51*/ "000000", false, true);
 
@@ -328,6 +329,7 @@ void CakeGame::testCake() {
 	runCakeTest(1, "232232432445", false);
 	runCakeTest(123, "4453766355133466", false);
 
+	_random.setSeed(oldSeed);
 	warning("finished CakeGame::testCake()");
 }
 
diff --git a/engines/groovie/logic/pente.cpp b/engines/groovie/logic/pente.cpp
index f0fb6dbb4f..6ee7a7b6c5 100644
--- a/engines/groovie/logic/pente.cpp
+++ b/engines/groovie/logic/pente.cpp
@@ -20,17 +20,1009 @@
  *
  */
 
+#include <limits.h>
 #include "groovie/groovie.h"
 #include "groovie/logic/pente.h"
+#include "common/stack.h"
 
 namespace Groovie {
 
+const uint WIN_SCORE = 100000000;
+const uint CAPTURE_SCORE = 1000000;
+
+struct pentePlayerTable {
+	Common::FixedStack<int, 813> lines;
+};
+
+struct penteTable {
+	pentePlayerTable player;
+	pentePlayerTable stauf;
+	uint playerScore;
+	uint staufScore;
+	byte playerLines;
+	byte staufLines;
+	byte width;
+	byte height;
+	uint16 boardSize;
+	byte lineLength;
+	uint16 moveCounter;
+	byte boardState[20][15];
+	uint16 linesCounter;
+	uint16 linesTable[20][15][21];
+	byte numAdjacentPieces[20][15];
+	byte calcTouchingPieces;// the deepest level of AI recursion sets this to 0, and then sets it back to 1 when returning
+};
+
+
+void PenteGame::penteSub02Frees(penteTable *table)
+{
+	delete table;
+}
+
+
+void PenteGame::penteSub05BuildLookupTable(penteTable *table)
+{
+	uint16 *puVar1;
+	uint _width;
+	uint uVar4;
+	uint uVar5;
+	byte bVar6;
+	byte local_14;
+	byte local_13;
+	uint16 lines_counter;
+	byte height;
+	byte line_length;
+	byte width;
+
+	width = table->width;
+	height = table->height;
+	bVar6 = 0;
+	lines_counter = 0;
+	line_length = table->lineLength;
+	_width = (uint)width;
+	
+	auto &lines_table = table->linesTable;
+	local_14 = 0;
+	if (height != 0) {
+		do {
+			local_13 = 0;
+			if (-1 < (int)(_width - line_length)) {
+				do {
+					bVar6 = 0;
+					if (line_length != 0) {
+						do {
+							uVar4 = (uint)bVar6;
+							bVar6 += 1;
+							puVar1 = lines_table[uVar4 + local_13][local_14];
+							*puVar1 = *puVar1 + 1;
+							puVar1 = lines_table[uVar4 + local_13][local_14];
+							puVar1[*puVar1] = lines_counter;
+						} while (bVar6 < line_length);
+					}
+					lines_counter += 1;
+					local_13 += 1;
+				} while ((int)(uint)local_13 <= (int)(_width - line_length));
+			}
+			local_14 += 1;
+		} while (local_14 < height);
+	}
+	local_13 = 0;
+	if (width != 0) {
+		do {
+			local_14 = 0;
+			if (-1 < (int)((uint)height - (uint)line_length)) {
+				do {
+					if (line_length != 0) {
+						uVar4 = 0;
+						do {
+							bVar6 = (char)uVar4 + 1;
+							puVar1 = lines_table[local_13][uVar4 + local_14];
+							*puVar1 = *puVar1 + 1;
+							puVar1 = lines_table[local_13][uVar4 + local_14];
+							puVar1[*puVar1] = lines_counter;
+							uVar4 = (uint)bVar6;
+						} while (bVar6 < line_length);
+					}
+					lines_counter += 1;
+					local_14 += 1;
+				} while ((int)(uint)local_14 <= (int)((uint)height - (uint)line_length));
+			}
+			local_13 += 1;
+		} while (local_13 < width);
+	}
+	local_14 = 0;
+	uVar4 = (uint)line_length;
+	if (-1 < (int)(height - uVar4)) {
+		do {
+			local_13 = 0;
+			if (-1 < (int)(_width - uVar4)) {
+				do {
+					if (line_length != 0) {
+						uVar5 = 0;
+						do {
+							width = (char)uVar5 + 1;
+							puVar1 = lines_table[local_13 + uVar5][uVar5 + local_14];
+							*puVar1 = *puVar1 + 1;
+							puVar1 = lines_table[local_13 + uVar5][uVar5 + local_14];
+							puVar1[*puVar1] = lines_counter;
+							uVar5 = (uint)width;
+						} while (width < line_length);
+					}
+					lines_counter += 1;
+					local_13 += 1;
+				} while ((int)(uint)local_13 <= (int)(_width - uVar4));
+			}
+			local_14 += 1;
+		} while ((int)(uint)local_14 <= (int)(height - uVar4));
+	}
+	local_14 = line_length - 1;
+	if (local_14 < height) {
+		do {
+			local_13 = 0;
+			if (-1 < (int)(_width - uVar4)) {
+				do {
+					if (line_length != 0) {
+						uVar5 = 0;
+						do {
+							width = (char)uVar5 + 1;
+							puVar1 = lines_table[local_13 + uVar5][local_14 - uVar5];
+							*puVar1 = *puVar1 + 1;
+							puVar1 = lines_table[local_13 + uVar5][local_14 - uVar5];
+							puVar1[*puVar1] = lines_counter;
+							uVar5 = (uint)width;
+						} while (width < line_length);
+					}
+					lines_counter += 1;
+					local_13 += 1;
+				} while ((int)(uint)local_13 <= (int)(_width - uVar4));
+			}
+			local_14 += 1;
+		} while (local_14 < height);
+	}
+	table->linesCounter = lines_counter;
+}
+
+
+
+
+penteTable *PenteGame::penteSub01Init(byte width, byte height, byte length)
+{
+	penteTable *table;
+	byte bVar5;
+	uint16 _height;
+
+	table = new penteTable();
+	table->width = width;
+	table->height = height;
+	_height = (uint16)height;
+	table->boardSize = _height * width;
+	bVar5 = 0;
+	table->lineLength = length;
+	memset(table->boardState, 0, sizeof(table->boardState));
+	
+	penteSub05BuildLookupTable(table);
+	assert(table->linesCounter == 812);
+	
+	bVar5 = 0;
+	table->staufScore = (uint)table->linesCounter;
+	table->playerScore = (uint)table->linesCounter;
+	memset(table->numAdjacentPieces, 0, sizeof(table->numAdjacentPieces));
+	
+	table->calcTouchingPieces = 1;
+	return table;
+}
+
+
+uint &getPlayerTable(penteTable *table, bool staufTurn, pentePlayerTable *&pt) {
+	pt = staufTurn ? &table->stauf : &table->player;
+	return staufTurn ? table->staufScore : table->playerScore;
+}
+
+
+void penteScoringLine(penteTable *table, uint16 lineIndex, bool stauf_turn, bool revert) {
+	pentePlayerTable *playerTable;
+	uint &score = getPlayerTable(table, stauf_turn, playerTable);
+
+	int lineLength, mult;
+	if (revert) {
+		lineLength = --playerTable->lines[lineIndex];
+		mult = -1;
+	} else {
+		lineLength = playerTable->lines[lineIndex]++;
+		mult = 1;
+	}
+
+	if (table->lineLength - lineLength == 1) {
+		score = (int)score + (int)WIN_SCORE * mult;
+	} else {
+		pentePlayerTable *opponentTable;
+		uint &opponentScore = getPlayerTable(table, !stauf_turn, opponentTable);
+		int opponentLineLength = opponentTable->lines[lineIndex];
+		if (lineLength == 0) {
+			opponentScore += (-(1 << ((byte)opponentLineLength & 0x1f))) * mult;
+			if (table->lineLength - opponentLineLength == 1) {
+				if (stauf_turn)
+					table->playerLines -= mult;
+				else
+					table->staufLines -= mult;
+			}
+		}
+		if (opponentLineLength == 0) {
+			score += (1 << ((byte)lineLength & 0x1f)) * mult;
+			if (table->lineLength - lineLength == 2) {
+				byte b;
+				if (stauf_turn)
+					b = (table->staufLines += mult);
+				else
+					b = (table->playerLines += mult);
+
+				if (revert)
+					b -= mult;
+
+				if (1 < b) {
+					score = (int)score + (int)CAPTURE_SCORE * mult;
+				}
+			}
+		}
+	}
+}
+
+void penteTouchingPieces(penteTable *table, byte moveX, byte moveY, bool revert) {
+	byte endX, endY;
+
+	if (moveX + 1 < table->width) {
+		endX = moveX + 1;
+	} else {
+		endX = table->width - 1;
+	}
+
+	if (moveY + 1 < table->height) {
+		endY = moveY + 1;
+	} else {
+		endY = table->height - 1;
+	}
+
+	byte x = 0;
+	if (1 < moveX) {
+		x = moveX - 1;
+	}
+
+	for (; x <= endX; x++) {
+		byte y = 0;
+		if (1 < moveY) {
+			y = moveY - 1;
+		}
+
+		for (; y <= endY; y++) {
+			if(revert)
+				table->numAdjacentPieces[x][y]--;
+			else
+				table->numAdjacentPieces[x][y]++;
+		}
+	}
+}
+
+
+void PenteGame::penteSub03Scoring(penteTable *table, byte move_y, byte move_x, bool stauf_turn) {
+	table->boardState[move_x][move_y] = stauf_turn ? 88 : 79;
+	uint16 lines = table->linesTable[move_x][move_y][0];
+
+	for (int i = 1; i <= lines; i++) {
+		uint16 lineIndex = table->linesTable[move_x][move_y][i];
+		penteScoringLine(table, lineIndex, stauf_turn, false);
+	}
+
+	if (table->calcTouchingPieces != 0) {
+		penteTouchingPieces(table, move_x, move_y, false);
+	}
+
+	table->moveCounter++;
+}
+
+
+void PenteGame::penteSub07RevertScore(penteTable *table, byte y, byte x) {
+	bool stauf_turn = table->boardState[x][y] == 88;
+	table->boardState[x][y] = 0;
+	table->moveCounter--;
+	uint lines = table->linesTable[x][y][0];
+
+	for (uint i = 1; i <= lines; i++) {
+		uint16 lineIndex = table->linesTable[x][y][i];
+		penteScoringLine(table, lineIndex, stauf_turn, true);
+	}
+
+	if (table->calcTouchingPieces != 0) {
+		penteTouchingPieces(table, x, y, true);
+	}
+}
+
+
+byte PenteGame::penteScoreCaptureSingle(penteTable *table, byte x, byte y, int slopeX, int slopeY) {
+	byte x1 = x + slopeX;
+	byte y1 = y + slopeY;
+	byte x2 = x + slopeX * 2;
+	byte y2 = y + slopeY * 2;
+	byte endX = x + slopeX * 3;
+	byte endY = y + slopeY * 3;
+
+	// we don't need to check for below 0 when we have unsigned types
+	if (x >= table->width || y >= table->height)
+		return 0;
+	if (endX >= table->width || endY >= table->height)
+		return 0;
+
+	auto &boardState = table->boardState;
+	byte captor = boardState[x][y];
+	byte captive = captor == 88 ? 79 : 88;
+
+	// make sure the captor is at the start and end of the line
+	if (boardState[endX][endY] != captor)
+		return 0;
+
+	// check that the captive is both of the middle pieces
+	if (boardState[x1][y1] != captive || boardState[x2][y2] != captive)
+		return 0;
+
+	penteSub07RevertScore(table, y1, x1);
+	penteSub07RevertScore(table, y2, x2);
+	return 1;
+}
+
+struct Slope {
+	int x, y;
+};
+// the order of these is important because we return the bitMask
+Slope slopes[] = {{1, 0},
+				  {1, 1},
+				  {0, 1},
+				  {-1, 1},
+				  {-1, 0},
+				  {-1, -1},
+				  {0, -1},
+				  {1, -1}};
+
+uint PenteGame::penteSub04ScoreCapture(penteTable *table, byte y, byte x)
+{
+	byte bitMask = 0;
+	bool isStauf = table->boardState[x][y] == 88;
+
+	
+	for (const Slope &slope : slopes ) {
+		bitMask <<= 1;
+		bitMask |= penteScoreCaptureSingle(table, x, y, slope.x, slope.y);
+	}
+
+	for (int i = bitMask; i; i >>= 1) {
+		if ((i & 1) == 0)
+			continue;
+		pentePlayerTable *playerTable;
+		uint &score = getPlayerTable(table, isStauf, playerTable);
+
+		int lineLength = ++playerTable->lines[table->linesCounter];
+		if (table->lineLength == lineLength) {
+			score += WIN_SCORE;
+		} else {
+			score += 1 << (lineLength - 1U & 0x1f);
+		}
+	}
+	return bitMask;
+}
+
+
+
+
+void PenteGame::penteSub08MaybeAnimateCapture(short move, byte *bitMaskG, short *param_3, short *param_4)
+
+{
+	byte x;
+	byte y;
+
+	x = (byte)((int)move / 0xf);
+	y = 0xe - (char)((int)move % 0xf);
+
+	byte &bitMask = *bitMaskG;
+	byte bVar3 = 0;
+	for (bVar3 = 0; bVar3 < 8; bVar3++) {
+		if (bitMask >> bVar3 & 1) {
+			bitMask = '\x01' << (bVar3 & 0x1f) ^ bitMask;
+			break;
+		}
+	}
+
+	short sVar4;
+	switch (bVar3) {
+	case 0:
+		*param_3 = (x + 2) * 0xf - (uint16)y;
+		*param_4 = ((uint16)x * 0xf - (uint16)y) + 0x2e;
+		return;
+	case 1:
+		*param_3 = (x + 1) * 0xf - (uint16)y;
+		*param_4 = ((uint16)x * 0xf - (uint16)y) + 0x10;
+		return;
+	case 2:
+		sVar4 = (uint16)x * 0xf - (uint16)y;
+		*param_3 = sVar4;
+		*param_4 = sVar4 + -0xe;
+		return;
+	case 3:
+		sVar4 = (uint16)x * 0xf - (uint16)y;
+		*param_3 = sVar4 + -1;
+		*param_4 = sVar4 + -0x10;
+		return;
+	case 4:
+		sVar4 = (uint16)x * 0xf - (uint16)y;
+		*param_3 = sVar4 + -2;
+		*param_4 = sVar4 + -0x12;
+		return;
+	case 5:
+		sVar4 = (uint16)x * 0xf - (uint16)y;
+		*param_3 = sVar4 + 0xd;
+		*param_4 = sVar4 + 0xc;
+		return;
+	case 6:
+		sVar4 = (uint16)x * 0xf - (uint16)y;
+		*param_3 = sVar4 + 0x1c;
+		*param_4 = sVar4 + 0x2a;
+		return;
+	case 7:
+		sVar4 = (uint16)x * 0xf - (uint16)y;
+		*param_3 = sVar4 + 0x1d;
+		*param_4 = sVar4 + 0x2c;
+	}
+	return;
+}
+
+
+
+
+void PenteGame::penteSub11RevertCapture(penteTable *table, byte y, byte x, byte bitMask)
+{
+	bool isPlayer = table->boardState[x][y] == 79;
+	for (int i = bitMask; i; i >>= 1) {
+		if ((i & 1) == 0)
+			continue;
+
+		pentePlayerTable *playerTable;
+		uint &score = getPlayerTable(table, !isPlayer, playerTable);
+
+		int linesCounter = --playerTable->lines[table->linesCounter];
+
+		if (table->lineLength - linesCounter == 1) {
+			score -= WIN_SCORE;
+		} else {
+			score -= (1 << ((char)linesCounter & 0x1f));
+		}
+	}
+
+	for (int i = 0; i < 8; i++) {
+		if ((bitMask >> i & 1) == 0)
+			continue;
+
+		Slope &slope = slopes[7 - i];
+		penteSub03Scoring(table, y + slope.y * 2, x + slope.x * 2, isPlayer);
+		penteSub03Scoring(table, y + slope.y, x + slope.x, isPlayer);
+	}
+}
+
+
+
+
+int PenteGame::penteSub10AiRecurse(penteTable *table_1, char depth, int parent_score)
+{
+	int iVar2;
+	int iVar3;
+	int iVar4;
+	bool bVar5;
+	uint uVar7;
+	int iVar8;
+	uint uVar9;
+	short sVar10;
+	int iVar12;
+	uint16 local_970[2];
+	int best_score;
+	Common::FixedStack<int, 600> local_95c;
+
+	best_score = 0x7fffffff;
+	if (depth == 1) {
+		table_1->calcTouchingPieces = 0;
+		for (byte bVar1 = 0; bVar1 < table_1->width; bVar1++) {
+			for (byte bVar11 = 0; bVar11 < table_1->height; bVar11++) {
+				if ((table_1->boardState[bVar1][bVar11] != 0) ||
+					(table_1->numAdjacentPieces[bVar1][bVar11] == 0)) {
+					continue;
+				}
+
+				penteSub03Scoring(table_1, bVar11, bVar1, (bool)((byte)table_1->moveCounter & 1));
+				uVar7 = penteSub04ScoreCapture(table_1, bVar11, bVar1);
+				if ((*(byte *)&table_1->moveCounter & 1) == 0) {
+					iVar12 = table_1->playerScore - table_1->staufScore;
+				} else {
+					iVar12 = table_1->staufScore - table_1->playerScore;
+				}
+				if ((byte)uVar7 != 0) {
+					penteSub11RevertCapture(table_1, bVar11, bVar1, (byte)uVar7);
+				}
+				penteSub07RevertScore(table_1, bVar11, bVar1);
+				if (iVar12 < best_score) {
+					best_score = iVar12;
+				}
+				if (-parent_score != best_score && parent_score <= -best_score) {
+					table_1->calcTouchingPieces = 1;
+					return -best_score;
+				}
+			}
+		}
+		table_1->calcTouchingPieces = 1;
+	} else {
+		*(uint *)local_970 = 0;
+		for (byte bVar1 = 0; bVar1 < table_1->width; bVar1++) {
+			for (byte bVar11 = 0; bVar11 < table_1->height; bVar11++) {
+				if ((table_1->boardState[bVar1][bVar11] != 0) ||
+					(table_1->numAdjacentPieces[bVar1][bVar11] == 0)) {
+					continue;
+				}
+
+				penteSub03Scoring(table_1, bVar11, bVar1, (bool)((byte)table_1->moveCounter & 1));
+				uVar7 = penteSub04ScoreCapture(table_1, bVar11, bVar1);
+				if ((*(byte *)&table_1->moveCounter & 1) == 0) {
+					iVar12 = table_1->playerScore - table_1->staufScore;
+				} else {
+					iVar12 = table_1->staufScore - table_1->playerScore;
+				}
+				if ((byte)uVar7 != 0) {
+					penteSub11RevertCapture(table_1, bVar11, bVar1, (byte)uVar7);
+				}
+				penteSub07RevertScore(table_1, bVar11, bVar1);
+				iVar8 = (int)(short)local_970[1];
+				*(uint *)local_970 = (uint)(uint16)(local_970[1] + 1) << 0x10;
+				local_95c[iVar8 * 2 + 1] = iVar12;
+				*(byte *)&local_95c[iVar8 * 2] = bVar11;
+				*((byte *)&local_95c[iVar8 * 2] + 1) = bVar1;
+			}
+		}
+
+		uint16 uVar6;
+		for (uVar6 = 1; uVar6 < local_970[1]; uVar6 = uVar6 * 3 + 1) {}
+
+		while (2 < (short)uVar6) {
+			uVar6 = (short)uVar6 / 3;
+			uVar7 = *(uint *)local_970 & 0xffff0000;
+			*(uint *)local_970 = uVar7 | uVar6;
+			local_970[1] = (uint16)(uVar7 >> 0x10);
+			if ((short)uVar6 < (short)local_970[1]) {
+				do {
+					bVar5 = false;
+					sVar10 = local_970[0] - uVar6;
+					while ((-1 < sVar10 && (!bVar5))) {
+						iVar8 = (int)sVar10;
+						iVar12 = (short)uVar6 + iVar8;
+						if (local_95c[iVar12 * 2 + 1] < local_95c[iVar8 * 2 + 1]) {
+							sVar10 -= uVar6;
+							iVar2 = local_95c[iVar12 * 2 + 1];
+							iVar3 = local_95c[iVar12 * 2];
+							iVar4 = local_95c[iVar8 * 2 + 1];
+							local_95c[iVar12 * 2] = local_95c[iVar8 * 2];
+							local_95c[iVar12 * 2 + 1] = iVar4;
+							local_95c[iVar8 * 2] = iVar3;
+							local_95c[iVar8 * 2 + 1] = iVar2;
+						} else {
+							bVar5 = true;
+						}
+					}
+					uVar7 = *(uint *)local_970 & 0xffff0000;
+					*(uint *)local_970 = uVar7 | (uint16)(local_970[0] + 1);
+					local_970[1] = (uint16)(uVar7 >> 0x10);
+				} while ((short)(local_970[0] + 1) < (short)local_970[1]);
+			}
+		}
+
+		local_970[1] = (uint16)(*(uint *)local_970 >> 0x10);
+		for (sVar10 = 0; sVar10 < local_970[1]; sVar10++) {
+			byte bVar1 = *(byte *)&local_95c[sVar10 * 2];
+			byte bVar11 = *((byte *)&local_95c[sVar10 * 2] + 1);
+			*(uint *)local_970 &= 0xffffff00;
+			penteSub03Scoring(table_1, bVar1, bVar11, (bool)((byte)table_1->moveCounter & 1));
+			uVar9 = penteSub04ScoreCapture(table_1, bVar1, bVar11);
+			uVar7 = table_1->playerScore;
+			if ((((int)uVar7 < WIN_SCORE) && ((int)table_1->staufScore < WIN_SCORE)) &&
+				(table_1->boardSize != table_1->moveCounter)) {
+				iVar12 = penteSub10AiRecurse(table_1, depth + -1, best_score);
+			} else {
+				if ((*(byte *)&table_1->moveCounter & 1) == 0) {
+					iVar12 = uVar7 - table_1->staufScore;
+				} else {
+					iVar12 = table_1->staufScore - uVar7;
+				}
+			}
+			if ((byte)uVar9 != 0) {
+				penteSub11RevertCapture(table_1, bVar1, bVar11, (byte)uVar9);
+			}
+			penteSub07RevertScore(table_1, bVar1, bVar11);
+			if (iVar12 < best_score) {
+				best_score = iVar12;
+			}
+			if (-parent_score != best_score && parent_score <= -best_score)
+				break;
+			local_970[1] = (uint16)(*(uint *)local_970 >> 0x10);
+		}
+	}
+	return -best_score;
+}
+
+
+
+
+uint PenteGame::penteSub09Ai(uint y_1, int param_2, int param_3, penteTable *table_4, byte depth)
+{
+	bool bVar1;
+	uint uVar2;
+	int iVar3;
+	byte _y;
+	int best_score;
+	uint16 uVar5;
+	byte bStack18;
+	byte _x;
+	uint16 local_c;
+	short local_4;
+	uint _y2;
+
+	bStack18 = 1;
+	_x = 0;
+	uVar5 = 0xffff;
+	best_score = 0x7fffffff;
+	if (table_4->width != 0) {
+		do {
+			_y = 0;
+			if (table_4->height != 0) {
+				do {
+					y_1 = (uint)_y;
+					if ((table_4->boardState[_x][y_1] == 0) && (table_4->numAdjacentPieces[_x][y_1] != 0)) {
+						penteSub03Scoring(table_4, _y, _x, (bool)((byte)table_4->moveCounter & 1));
+						_y2 = penteSub04ScoreCapture(table_4, _y, _x);
+						if (((int)table_4->playerScore < WIN_SCORE) &&
+							((int)table_4->staufScore < WIN_SCORE)) {
+							bVar1 = false;
+						} else {
+							bVar1 = true;
+						}
+						if ((byte)_y2 != 0) {
+							penteSub11RevertCapture(table_4, _y, _x, (byte)_y2);
+						}
+						penteSub07RevertScore(table_4, _y, _x);
+						if (bVar1) {
+							return y_1 & 0xffff0000 | (uint)(uint16)((uint16)_y + (uint16)_x * 100);
+						}
+					}
+					_y += 1;
+				} while (_y <= table_4->height && table_4->height != _y);
+			}
+			_x += 1;
+			y_1 &= 0xffffff00;
+		} while (_x <= table_4->width && table_4->width != _x);
+	}
+	do {
+		_x = 0;
+		if (table_4->width != 0) {
+			do {
+				_y2 = 0;
+				if (table_4->height != 0) {
+					do {
+						_y = (byte)_y2;
+						y_1 = 0;
+						if ((table_4->boardState[_x][_y2] == 0) && (table_4->numAdjacentPieces[_x][_y2] != 0)) {
+							penteSub03Scoring(table_4, _y, _x, (bool)((byte)table_4->moveCounter & 1));
+							uVar2 = penteSub04ScoreCapture(table_4, _y, _x);
+							iVar3 = penteSub10AiRecurse(table_4, depth - 1, best_score);
+							if ((byte)uVar2 != 0) {
+								penteSub11RevertCapture(table_4, _y, _x, (byte)uVar2);
+							}
+							penteSub07RevertScore(table_4, _y, _x);
+							local_c = (uint16)_x;
+							local_4 = (short)_y2;
+							if (iVar3 < best_score) {
+								bStack18 = 1;
+								uVar5 = local_c * 100 + local_4;
+								best_score = iVar3;
+							} else {
+								if (iVar3 == best_score) {
+									bStack18 += 1;
+									_y2 = _random.getRandomNumber(UINT_MAX);
+									y_1 = (uint)bStack18;
+									if ((_y2 % CAPTURE_SCORE) * y_1 < CAPTURE_SCORE) {
+										uVar5 = local_c * 100 + local_4;
+									}
+								}
+							}
+						}
+						_y += 1;
+						_y2 = (uint)_y;
+					} while (_y <= table_4->height && table_4->height != _y);
+				}
+				_x += 1;
+				y_1 &= 0xffffff00;
+			} while (_x <= table_4->width && table_4->width != _x);
+		}
+	} while ((99999999 < best_score) && (depth -= 1, 1 < depth));
+	return y_1 & 0xffff0000 | (uint)uVar5;
+}
+
+
+int varsMoveToXY(byte var0, byte var1, byte var2, byte &x, byte &y) {
+	int move = ((char)var0 * 10 + (short)(char)var1) * 10 + (short)(char)var2;
+	x = (byte)(move / 15);
+	y = 0xe - (char)((int)move % 15);
+	return move;
+}
+
+void aiMoveToXY(int move, byte &x, byte &y) {
+	x = move / 100;
+	y = move % 100;
+}
+
+void moveToVars(int move, byte &var0, byte &var1, byte &var2) {
+	var0 = (byte)(move / 100);
+	var1 = (byte)((move % 100) / 10);
+	var2 = (byte)(move % 10);
+}
+
+void moveXYToVars(uint x, uint y, byte &var0, byte &var1, byte &var2) {
+	int move = (x * 0xf - y) + 0xe;
+	moveToVars(move, var0, var1, var2);
+}
+
+void PenteGame::penteOp(byte *vars)
+{
+	uint16 uVar1;
+	int iVar2;
+	uint uVar3;
+	byte ai_depth;
+	short local_2;
+
+	if ((game_state_table == (penteTable *)0x0) && (vars[4] != 0)) {
+		game_state_table = penteSub01Init(0x14, 0xf, 5);
+	}
+	uVar3 = vars[4];
+	debugC(kDebugLogic, "penteOp vars[4]: %d", (int)vars[4]);
+
+	switch (uVar3) {
+	case 0:
+		if (game_state_table != (penteTable *)0x0) {
+			penteSub02Frees(game_state_table);
+		}
+		game_state_table = (penteTable *)0x0;
+		return;
+	case 1:
+		globalPlayerMove = varsMoveToXY(vars[0], vars[1], vars[2], globalX, globalY);
+		debugC(kDebugLogic, "player moved to %d, %d", (int)globalX, (int)globalY);
+		penteSub03Scoring(game_state_table, globalY, globalX,
+										 (bool)((byte)game_state_table->moveCounter & 1));
+		uVar3 = penteSub04ScoreCapture(game_state_table, globalY, globalX);
+		global2 = (char)uVar3;
+		return;
+	case 2:
+	case 4:
+		if (global2 != '\0') {
+			if (global1 < 0) {
+				penteSub08MaybeAnimateCapture(globalPlayerMove, (byte *)&global2, &local_2, &global1);
+				vars[5] = 1;
+				moveToVars(local_2, vars[0], vars[1], vars[2]);
+				return;
+			}
+		LAB_00412da4:
+			vars[0] = (byte)((int)global1 / 100);
+			vars[1] = (byte)((int)(global1 % 100) / 10);
+			iVar2 = (int)global1;
+			vars[2] = (byte)(iVar2 % 10);
+			global1 = -1;
+			vars[5] = 1;
+			return;
+		}
+		if (-1 < global1)
+			goto LAB_00412da4;
+		if ((int)game_state_table->playerScore < WIN_SCORE) {
+			if (((int)game_state_table->staufScore < WIN_SCORE) &&
+				(uVar1 = game_state_table->moveCounter)) {
+				vars[5] = 0;
+				return;
+			}
+			if ((int)game_state_table->playerScore < WIN_SCORE) {
+				uVar3 = game_state_table->staufScore;
+				vars[5] = 2;// Stauf wins
+				if ((int)uVar3 < WIN_SCORE) {
+					vars[5] = 4;// player wins because the board is full?
+				}
+				goto DEALLOC;
+			}
+		}
+		vars[5] = 3;// player wins
+	DEALLOC:
+		penteSub02Frees(game_state_table);
+		game_state_table = (penteTable *)0x0;
+		return;
+	case 3:
+		break;
+	case 5:
+		// asking Samantha to make a move? this does a bunch of queries to check if pieces belong to stauf or the player?
+		byte x, y;
+		varsMoveToXY(vars[0], vars[1], vars[2], x, y);
+		ai_depth = game_state_table->boardState[x][y];
+		if (ai_depth == 0) {
+			vars[3] = 0;
+			return;
+		}
+		if (ai_depth == 0x4f) {
+			vars[3] = 2;
+			return;
+		}
+		if (ai_depth != 0x58) {
+			return;
+		}
+		vars[3] = 1;
+	default:
+		return;
+	}
+	ai_depth = vars[6];
+	if (ai_depth == 0) {
+		ai_depth = 3;
+	} else {
+		if (ai_depth == 1) {
+			ai_depth = 4;
+		} else {
+			if (ai_depth != 2)
+				goto LAB_00412e85;
+			ai_depth = 5;
+		}
+	}
+	globalPlayerMove = penteSub09Ai(0, 0, 0, game_state_table, ai_depth);
+LAB_00412e85:
+	aiMoveToXY(globalPlayerMove, globalX, globalY);
+	debugC(kDebugLogic, "Stauf moved to %d, %d", (int)globalX, (int)globalY);
+	penteSub03Scoring(game_state_table, globalY, globalX,
+									 (bool)((byte)game_state_table->moveCounter & 1));
+	uVar3 = penteSub04ScoreCapture(game_state_table, globalY, globalX);
+	global2 = (char)uVar3;
+	globalPlayerMove = ((uint16)globalX * 0xf - (uint16)globalY) + 0xe;
+	moveXYToVars(globalX, globalY, vars[0], vars[1], vars[2]);
+}
+
+
 PenteGame::PenteGame() : _random("PenteGame") {
+	global1 = -1;
+	game_state_table = NULL;
+	globalY = 0;
+	globalX = 0;
+	global2 = 0;
+	globalPlayerMove = 0;
+#if 0
+	test();
+#endif
 }
 
 void PenteGame::run(byte *scriptVariables) {
-	// TODO
-	scriptVariables[5] = 4;	// Auto-solve the puzzle, so the player can continue	
+	// TODO: don't need to copy these variables once this is cleaned up
+	byte tvars[1024];
+	memcpy(tvars, scriptVariables, sizeof(tvars));
+	penteOp(tvars);
+	for (int i = 0; i < sizeof(tvars); i++) {
+		if (tvars[i] != scriptVariables[i]) {
+			debugC(kDebugLogic, "PenteGame::run var %d changed from %d to %d", i, (int)scriptVariables[i], (int)tvars[i]);
+		}
+	}
+	memcpy(scriptVariables, tvars, sizeof(tvars));
+}
+
+void PenteGame::test() {
+	warning("starting PenteGame::test()");
+	uint32 oldSeed = _random.getSeed();
+
+	// 6 moves per line
+	testGame(3,
+		{
+			/*x=*/10, /*y=*/6, /*x=*/9, /*y=*/6, /*x=*/10, /*y=*/7, /*x=*/10, /*y=*/5, /*x=*/10, /*y=*/8, /*x=*/9, /*y=*/9,
+			/*x=*/10, /*y=*/9, /*x=*/10, /*y=*/10, /*x=*/9, /*y=*/8, /*x=*/8, /*y=*/7, /*x=*/8, /*y=*/8, /*x=*/7, /*y=*/8,
+			/*x=*/6, /*y=*/9, /*x=*/11, /*y=*/4, 
+		}, false);
+
+	testGame(10,
+		{
+			/*x=*/10, /*y=*/6, /*x=*/11, /*y=*/7, /*x=*/10, /*y=*/5, /*x=*/10, /*y=*/7, /*x=*/9, /*y=*/7, /*x=*/12, /*y=*/7,
+			/*x=*/10, /*y=*/4, /*x=*/8, /*y=*/8, /*x=*/10, /*y=*/3, /*x=*/11, /*y=*/5, /*x=*/10, /*y=*/2, /*x=*/9, /*y=*/7,
+			/*x=*/10, /*y=*/6, 
+		}, true);
+
+	// test bottom left corner
+	testGame(1993,
+		{
+			/*x=*/0, /*y=*/0, /*x=*/1, /*y=*/1, /*x=*/1, /*y=*/0, /*x=*/2, /*y=*/0, /*x=*/0, /*y=*/1, /*x=*/0, /*y=*/2,
+			/*x=*/2, /*y=*/1, /*x=*/3, /*y=*/2, /*x=*/1, /*y=*/2, /*x=*/2, /*y=*/3, /*x=*/4, /*y=*/1, /*x=*/1, /*y=*/4,
+			/*x=*/5, /*y=*/1, /*x=*/6, /*y=*/1, /*x=*/3, /*y=*/0, /*x=*/5, /*y=*/2, /*x=*/4, /*y=*/3, /*x=*/3, /*y=*/1,
+			/*x=*/3, /*y=*/3, /*x=*/5, /*y=*/3, /*x=*/4, /*y=*/1, /*x=*/4, /*y=*/3, /*x=*/3, /*y=*/3, /*x=*/3, /*y=*/4,
+			/*x=*/2, /*y=*/5, /*x=*/7, /*y=*/0
+		}, false);
+
+	// test bottom right corner
+	testGame(1995,
+		{
+			/*x=*/19, /*y=*/0, /*x=*/18, /*y=*/1, /*x=*/19, /*y=*/1, /*x=*/18, /*y=*/2, /*x=*/18, /*y=*/0, /*x=*/18, /*y=*/3,
+			/*x=*/18, /*y=*/4, /*x=*/17, /*y=*/5, /*x=*/17, /*y=*/0, /*x=*/16, /*y=*/5, /*x=*/17, /*y=*/4, /*x=*/16, /*y=*/4,
+			/*x=*/18, /*y=*/5, /*x=*/18, /*y=*/6, /*x=*/18, /*y=*/5, /*x=*/15, /*y=*/3, /*x=*/18, /*y=*/4, /*x=*/14, /*y=*/2, 
+		}, false);
+
+	// test top left corner
+	testGame(1996,
+		{
+			/*x=*/0, /*y=*/14, /*x=*/1, /*y=*/13, /*x=*/1, /*y=*/14, /*x=*/2, /*y=*/14, /*x=*/0, /*y=*/13, /*x=*/0, /*y=*/12,
+			/*x=*/1, /*y=*/12, /*x=*/2, /*y=*/11, /*x=*/2, /*y=*/12, /*x=*/3, /*y=*/12, /*x=*/4, /*y=*/13, /*x=*/1, /*y=*/10,
+			/*x=*/0, /*y=*/9, /*x=*/3, /*y=*/10, /*x=*/1, /*y=*/12, /*x=*/4, /*y=*/9, /*x=*/5, /*y=*/8, /*x=*/6, /*y=*/9,
+			/*x=*/3, /*y=*/11, /*x=*/6, /*y=*/10, /*x=*/6, /*y=*/11, /*x=*/4, /*y=*/8, /*x=*/3, /*y=*/9, /*x=*/4, /*y=*/10,
+			/*x=*/4, /*y=*/11, /*x=*/2, /*y=*/10, /*x=*/0, /*y=*/10, /*x=*/5, /*y=*/10
+		}, false);
+
+	// test top right corner
+	testGame(2019,
+		{
+			/*x=*/19, /*y=*/14, /*x=*/18, /*y=*/13, /*x=*/19, /*y=*/12, /*x=*/18, /*y=*/12, /*x=*/18, /*y=*/11, /*x=*/17, /*y=*/10,
+			/*x=*/18, /*y=*/14, /*x=*/16, /*y=*/11, /*x=*/18, /*y=*/9, /*x=*/15, /*y=*/12, /*x=*/14, /*y=*/13, /*x=*/15, /*y=*/10,
+			/*x=*/15, /*y=*/11, /*x=*/14, /*y=*/10, /*x=*/17, /*y=*/12, /*x=*/16, /*y=*/10, /*x=*/13, /*y=*/10, /*x=*/18, /*y=*/10
+		}, false);
+
+	_random.setSeed(oldSeed);
+	warning("finished PenteGame::test()");
+}
+
+void PenteGame::testGame(uint32 seed, Common::Array<int> moves, bool playerWin) {
+	byte vars[1024];
+	byte &winner = vars[5];
+	byte &op = vars[4];
+
+	warning("starting PenteGame::testGame(%u, %u, %d)", seed, moves.size(), (int)playerWin);
+	memset(vars, 0, sizeof(vars));
+	_random.setSeed(seed);
+
+	op = 0;
+	penteOp(vars);
+
+	for (uint i = 0; i < moves.size(); i += 2) {
+		if (winner)
+			error("%u: early winner: %d", i, (int)winner);
+
+		int x = moves[i];
+		int y = moves[i + 1];
+
+		if (i % 4) {
+			// check Stauf's move
+			op = 3;
+			penteOp(vars);
+
+			byte sX, sY;
+			varsMoveToXY(vars[0], vars[1], vars[2], sX, sY);
+
+			if (sX != x || sY != y)
+				error("%u: Stauf, expected (%d, %d), got (%d, %d)", i, (int)x, (int)y, (int)sX, (int)sY);
+
+			do {
+				op = 4;
+				penteOp(vars);
+			} while (winner == 1);
+			continue;
+		}
+
+		moveXYToVars(x, y, vars[0], vars[1], vars[2]);
+		op = 1;
+		penteOp(vars);
+
+		do {
+			op = 2;
+			penteOp(vars);
+		} while (winner == 1);
+	}
+
+	if (playerWin && winner != 3)
+		error("player didn't win, winner: %d", (int)winner);
+	else if (playerWin == false && winner != 2)
+		error("Stauf didn't win, winner: %d", (int)winner);
+
+	warning("finished PenteGame::testGame(%u, %u, %d)", seed, moves.size(), (int)playerWin);
 }
 
 } // End of Groovie namespace
diff --git a/engines/groovie/logic/pente.h b/engines/groovie/logic/pente.h
index 1cc47541c5..457794ab53 100644
--- a/engines/groovie/logic/pente.h
+++ b/engines/groovie/logic/pente.h
@@ -31,13 +31,40 @@ namespace Groovie {
 /*
  * Pente puzzle at the end of the game.
  */
+
+struct pentePlayerTable;
+struct penteTable;
+
 class PenteGame {
 public:
 	PenteGame();
 	void run(byte *scriptVariables);
-	
+
 private:
+	int *allocs(int param_1, int param_2);
+	void penteSub02Frees(penteTable *param_1);
+	void penteSub05BuildLookupTable(penteTable *table);
+	penteTable *penteSub01Init(byte width, byte height, byte length);
+	void penteSub03Scoring(penteTable *table, byte move_y, byte move_x, bool whose_turn);
+	void penteSub07RevertScore(penteTable *table_1, byte y, byte x);
+	byte penteScoreCaptureSingle(penteTable *table, byte x, byte y, int slopeX, int slopeY);
+	uint penteSub04ScoreCapture(penteTable *table, byte y, byte x);
+	void penteSub08MaybeAnimateCapture(short param_1, byte *param_2, short *param_3, short *param_4);
+	void penteSub11RevertCapture(penteTable *table, byte y, byte x, byte y2);
+	int penteSub10AiRecurse(penteTable *table_1, char depth, int parent_score);
+	uint penteSub09Ai(uint y_1, int param_2, int param_3, penteTable *table_4, byte depth);
+	void penteOp(byte *vars);
+	void test();
+	void testGame(uint32 seed, Common::Array<int> moves, bool playerWin);
+
 	Common::RandomSource _random;
+
+	byte globalY;
+	byte globalX;
+	char global2;
+	short globalPlayerMove;
+	short global1;
+	penteTable *game_state_table;
 };
 
 } // End of Groovie namespace
diff --git a/engines/groovie/logic/triangle.cpp b/engines/groovie/logic/triangle.cpp
index e4ff554c16..2eac2b2fd5 100644
--- a/engines/groovie/logic/triangle.cpp
+++ b/engines/groovie/logic/triangle.cpp
@@ -824,6 +824,7 @@ void TriangleGame::ensureSamanthaWin(uint32 seed) {
 
 void TriangleGame::test() {
 	warning("starting TriangleGame::test");
+	uint32 oldSeed = _random.getSeed();
 
 	// Samantha appears to not always win, but she usually does, and she wins these seeds
 	// haven't verified if she always wins in the original game
@@ -837,6 +838,7 @@ void TriangleGame::test() {
 	testGame(3, {24, 32, 17, 42, 23, 53, 16, 39, 11, 29, 10, 44, 6, 33, 7, 63, 12, 28, 18, 31, 13, 204, 8, 204, 4, 38, 3, 43}, false);
 	testGame(3, {6, 32, 10, 42, 11, 53, 7, 23, 3, 15, 12, 22, 18, 43, 13, 33, 8, 35, 4, 31, 1, 204, 17, 204, 16, 204, 19, 63 }, false);
 
+	_random.setSeed(oldSeed);
 	warning("finished TriangleGame::test");
 }
 
diff --git a/engines/groovie/script.cpp b/engines/groovie/script.cpp
index 8b71a825d9..a4e8591744 100644
--- a/engines/groovie/script.cpp
+++ b/engines/groovie/script.cpp
@@ -2181,6 +2181,26 @@ void Script::o2_playsound() {
 	playBackgroundSound(fileref, loops);
 }
 
+void Script::o_wipemaskfromstring58() {
+	// used in pente when pieces are captured
+	Common::String vidName;
+	uint16 instStart = _currentInstruction;
+	uint32 fileref = getVideoRefString(vidName);
+	setBitFlag(10, true);
+
+	// Show the debug information just when starting the playback
+	if (fileref != _videoRef) {
+		debugC(0, kDebugScript, "Groovie::Script: WIPEMASKFROMSTRING58 %d ('%s')", fileref, vidName.c_str());
+		debugC(2, kDebugVideo, "\nGroovie::Script: @0x%04X: Playing mask video %d ('%s') via 0x58 (o_wipemaskfromstring58)", instStart - 1, fileref, vidName.c_str());
+	}
+
+	// Play the video
+	if (!playvideofromref(fileref)) {
+		// Move _currentInstruction back
+		_currentInstruction = instStart - 1;
+	}
+}
+
 void Script::o2_check_sounds_overlays() {
 	uint16 val1 = readScript8or16bits();
 	uint8 val2 = readScript8bits();
@@ -2394,7 +2414,7 @@ Script::OpcodeFunc Script::_opcodesV2[NUM_OPCODES] = {
 	&Script::o2_setscriptend,
 	&Script::o2_playsound,
 	&Script::o_invalid,
-	&Script::o_invalid, // 0x58
+	&Script::o_wipemaskfromstring58, // 0x58
 	&Script::o2_check_sounds_overlays,
 	&Script::o2_preview_loadgame
 };
diff --git a/engines/groovie/script.h b/engines/groovie/script.h
index b9131d80c9..ac42f8c3fa 100644
--- a/engines/groovie/script.h
+++ b/engines/groovie/script.h
@@ -245,6 +245,7 @@ private:
 	void o_musicdelay();
 	void o_hotspot_outrect();
 	void o_stub56();
+	void o_wipemaskfromstring58();
 	void o_stub59();
 
 	void o2_bf0on();
diff --git a/engines/groovie/video/roq.cpp b/engines/groovie/video/roq.cpp
index 44c601b5dd..a81635c3f7 100644
--- a/engines/groovie/video/roq.cpp
+++ b/engines/groovie/video/roq.cpp
@@ -65,13 +65,20 @@ static inline void copyPixel(byte *dst, const byte *src) {
 	*(uint32 *)dst = *(const uint32 *)src;
 }
 
-// Overwrites one pixel of destination regardless of the alpha value
+// Overwrites one pixel of destination if the src pixel is visible
 static inline void copyPixelIfAlpha(byte *dst, const byte *src) {
 	if (src[kAIndex] > 0) {
 		copyPixel(dst, src);
 	}
 }
 
+// Overwrites one pixel if it's part of the mask
+static inline void copyPixelIfMask(byte *dst, const byte *mask, const byte *src) {
+	if (mask[kAIndex] > 0) {
+		copyPixel(dst, src);
+	}
+}
+
 // Copies one pixel to destination but respects the alpha value of the source
 static inline void copyPixelWithA(byte *dst, const byte *src) {
 	if (src[kAIndex] == 255) {
@@ -142,6 +149,7 @@ uint16 ROQPlayer::loadInternal() {
 	_flagOne = ((_flags & (1 << 1)) != 0);
 	_flagTwo = ((_flags & (1 << 2)) != 0);
 	_altMotionDecoder = ((_flags & (1 << 14)) != 0);
+	_flagMasked = ((_flags & (1 << 10)) != 0);
 
 	// Read the file header
 	ROQBlockHeader blockHeader;
@@ -249,7 +257,13 @@ void ROQPlayer::buildShowBuf() {
 
 	// Select the destination buffer according to the given flags
 	int destOffset = 0;
-	Graphics::Surface *destBuf;
+	Graphics::Surface *maskBuf = NULL;
+	Graphics::Surface *srcBuf = _currBuf;
+	Graphics::Surface *destBuf = NULL;
+	if (_flagMasked) {
+		srcBuf = _bg;
+		maskBuf = _currBuf;
+	}
 	if (_flagOne) {
 		if (_flagTwo) {
 			destBuf = _overBuf;
@@ -266,17 +280,24 @@ void ROQPlayer::buildShowBuf() {
 	int startX, startY, stopX, stopY;
 	calcStartStop(startX, stopX, _origX, _screen->w);
 	calcStartStop(startY, stopY, _origY, _screen->h);
-	assert(destBuf->format == _currBuf->format);
+	assert(destBuf->format == srcBuf->format);
 	assert(destBuf->format == _overBuf->format);
 	assert(destBuf->format.bytesPerPixel == 4);
 
 	for (int line = startY; line < stopY; line++) {
-		byte *in = (byte *)_currBuf->getBasePtr(MAX(0, -_origX) / _scaleX, (line - _origY) / _scaleY);
+		byte *in = (byte *)srcBuf->getBasePtr(MAX(0, -_origX) / _scaleX, (line - _origY) / _scaleY);
 		byte *inOvr = (byte *)_overBuf->getBasePtr(startX, line);
 		byte *out = (byte *)destBuf->getBasePtr(startX, line + destOffset);
+		byte *mask = NULL;
+		if (_flagMasked) {
+			mask = (byte *)maskBuf->getBasePtr(MAX(0, -_origX) / _scaleX, (line - _origY) / _scaleY);
+		}
+
 
 		for (int x = startX; x < stopX; x++) {
-			if (destBuf == _overBuf) {
+			if (_flagMasked) {
+				copyPixelIfMask(out, mask, in);
+			} else if (destBuf == _overBuf) {
 				copyPixelIfAlpha(out, in);
 			} else {
 				copyPixelWithA(out, in);
@@ -296,6 +317,8 @@ void ROQPlayer::buildShowBuf() {
 			inOvr += _screen->format.bytesPerPixel;
 			if (!(x % _scaleX))
 				in += _screen->format.bytesPerPixel;
+			if (mask)
+				mask += _screen->format.bytesPerPixel;
 		}
 	}
 
diff --git a/engines/groovie/video/roq.h b/engines/groovie/video/roq.h
index 32e3964934..3bfd52fb1f 100644
--- a/engines/groovie/video/roq.h
+++ b/engines/groovie/video/roq.h
@@ -93,9 +93,10 @@ private:
 	byte _codebook4[256 * 4];
 
 	// Flags
-	bool _flagOne;	// Play only first frame and do not print the image to the screen
-	bool _flagTwo;	// If _flagOne is set. Copy frame to the foreground otherwise to the background
+	bool _flagOne;	 //!< Play only first frame and do not print the image to the screen
+	bool _flagTwo;	 //!< If _flagOne is set. Copy frame to the foreground otherwise to the background
 	bool _altMotionDecoder; // Some ROQ vids use a variation on the copy codeblock
+	bool _flagMasked; //!< Clear the video instead of play it, used in pente
 
 	// Buffers
 	void buildShowBuf();




More information about the Scummvm-git-logs mailing list