How to use minimax alalgorithm without recursion? - recursion

I'm developing a program that people can play "tic tac toe" with a computer.
I choose structured text as my develop language, but it doesn't have recursion, so I have to develop it without recursion.
As the result, I decide to use the stack to instead, but I don't know how to change recursion into the stack.
I try to use stack like BFS, and also I wanna that minimax can make the best move.

I don't know Structured Text, but maybe it helps to see a solution in another language.
You need a stack with moves, so that when backtracking, you know from where to start to search for a next, alternative move for it.
You also need a stack of scores, so you know the best score so far at every depth of the minimax search tree.
The two stacks could be combined into one stack when you can store the two data in one structure to be placed on the single stack. But in the below implementation in JavaScript I have tried to keep the data structures as simple as possible and used two stacks (fix-sized arrays with a separate size variable). All variables are declared at the top of the functions (as in Structured Text), and return statements are always at the end of the function bodies (since there is no return statement in Structured Text).
function gameWon(board) {
/* Looks at all possible lines of 3,
* to see if they are occupied by three of the same symbols
* board: is a 1D array with 9 integers:
* 1 = "X" (first player), 0 = free, 1 = "O"
* Returns boolean.
*/
let value;
let i;
// In Structured Text you'd not have gameWonReturn,
// as you would use `gameWon` instead for returning a value
let gameWonReturn = false;
for (i = 0; i < 3; i++) {
value = board[i*3];
if (value != 0 && value == board[i*3+1] && value == board[i*3+2]) {
gameWonReturn = true;
break;
}
value = board[i];
if (value != 0 && value == board[i+3] && value == board[i+6]) {
gameWonReturn = true;
break;
}
if (value != 0 && value == board[4] && value == board[8-i]) {
gameWonReturn = true;
break;
}
}
return gameWonReturn;
}
function play(board, moveStack, moveCount, move) {
let playReturn;
// Derive played symbol from number of moves played
board[move] = 1 - (moveCount % 2) * 2; // Use MOD operator. Result is 1 or -1
moveStack[moveCount] = move; // Log move in stack
playReturn = moveCount + 1;
return playReturn;
}
function minimax(board, moveStack, moveCount) {
const scoreStack = Array(10); // Array of signed integers
const originalMoveCount = moveCount;
let move; // -1..9
let score; // -10..10
let player; // -1 or 1
let minimaxReturn = -1; // -1..9
while (true) {
// Current player can be derived from the number of moves that were played
player = 1 - (moveCount % 2) * 2; // 1 = First, maximizing player. -1 = Second, minimizing player
// Check for game-over
if (gameWon(board)) { // Preceding move resulted in win for previous player
scoreStack[moveCount] = -(10 - moveCount) * player; // Earlier wins get greater score
} else if (moveCount == 9) { // It's a draw
scoreStack[moveCount] = 0;
} else {
moveStack[moveCount] = -1; // Prepare for iterating moves for current player
scoreStack[moveCount] = -player * 10; // Initialize with worst possible score for current player
moveCount++;
}
do { // Repeat:
moveCount--;
if (moveCount < originalMoveCount) { // All done
break; // Exit point
}
// Look for a next, valid move
move = moveStack[moveCount];
if (move >= 0) { // Was a valid move: derive score from deeper results
score = scoreStack[moveCount + 1]; // Best score opponent can get
if ((board[move] == 1) == (score > scoreStack[moveCount])) { // Improvement
scoreStack[moveCount] = score;
if (moveCount == originalMoveCount) {
minimaxReturn = move; // For now this is a best move...
}
}
// Take back move
board[move] = 0;
}
// Look for next valid move
do {
move++;
} while (move < 9 && board[move] != 0) // Occupied
} while (move == 9); // Backtrack (i.e. repeat loop) when all moves were tried
if (moveCount < originalMoveCount) { // All done
break; // Exit point
}
// Play move
moveCount = play(board, moveStack, moveCount, move);
}
return minimaxReturn;
}
function main() {
const board = Array(9).fill(0); // An empty 3x3 board as 1 dimensional array
const moveStack = Array(10); // History of played moves (indices in board)
let moveCount = 0; // Number of moves played
let bestMove;
/* Demo:
* Play moves to arrive at this board:
*
* X | O |
* ---+---+---
* O | |
* ---+---+---
* X | |
*/
moveCount = play(board, moveStack, moveCount, 0); // Play X in top-left
moveCount = play(board, moveStack, moveCount, 1); // Play O next to it
moveCount = play(board, moveStack, moveCount, 6); // Play X in bottom-left
moveCount = play(board, moveStack, moveCount, 3); // Play O in middle row
// Run minimax for suggesting where to play an "X":
bestMove = minimax(board, moveStack, moveCount);
console.log(bestMove); // 4 (center of the board)
}
main();

Related

Sensible way to encode user defined sort-order in back end database?

I have a series of "rows" in a collection which are persisted to a nosql database (in this case Firestore). Each one of my rows has a sort order which is established when the user adds, inserts, copies or moves rows with the collection. The insertion point into which a user may insert new records is arbitrary. The sort order is persisted to the backend, and can be retrieved ordered by the sort order field. There may be a large number of rows in the collection, on the order 50K.
The question what is the sort order encoding format that would permit repeated insertion of new records between existing records, without having to occasionally rewrite the sort index of the entire collection to provide "space" in the sort order between adjacent records.
I'm guessing there may some standard way to achieve this, but not sure what it is.
Assume the alphabet is "abc". Then:
b, c, cb...
is a lexicographically sorted list that allows you to insert items anywhere:
ab, b, bb, c, cab, cb, cbb...
And the result is still a list that allows you to insert items anywhere:
aab, ab, ac, b, bab, bb, bc, c, caab, cab, cac, cb, cbab, cbb, cbbb...
The trick is to avoid having "a" as the last character of an item, so that you can always put items behind others.
Do this with 64 ASCII characters instead of 3.
I've been thinking about this for quite a few months. This is my progress so far in implementing it. It still has some flaws and it's a little bit of a mess, but I guess I'll clean it and upload it at npm when I find more time.
// Originally written in TypeScript, then removed the types for SO.
const alphabet = 'abc';
function getHigherAsciiChar(char) {
const index = alphabet.indexOf(char);
if (index === alphabet.length - 1) {
return ''; // sorry, there's no higher character
}
const nextIndex = Math.ceil((index + alphabet.length - 1) / 2);
return alphabet.charAt(nextIndex);
}
function getCharBetween(minChar, maxChar) {
if (minChar > maxChar) {
throw new Error('minChar > maxChar, ' + minChar + ' > ' + maxChar);
}
const minIndex = alphabet.indexOf(minChar);
const maxIndex = alphabet.indexOf(maxChar);
const nextIndex = Math.floor((minIndex + maxIndex) / 2);
if (nextIndex === minIndex) {
return ''; // there is no character between these two
}
return alphabet.charAt(nextIndex);
}
function getPaddedString(finalLength, string) {
let result = string;
while (result.length < finalLength) {
result += alphabet.charAt(0);
}
return result;
}
function getOrderString(bounds) {
const console = { log: () => {} }; // uncomment this to log debug stuff
if (!bounds.previous && !bounds.next) {
return getHigherAsciiChar(alphabet[0]);
}
const previousString = bounds.previous || '';
if (!bounds.next) {
const firstPreviousChars = previousString.substr(0, previousString.length - 1);
const lastPreviousChar = previousString.charAt(previousString.length - 1);
return firstPreviousChars + (
getHigherAsciiChar(lastPreviousChar) || (
lastPreviousChar + getHigherAsciiChar(alphabet.charAt(0))
)
);
}
const nextString = bounds.next;
console.log(`Searching between '${previousString}' and '${nextString}'...`);
const bigStringLength = Math.max(previousString.length, nextString.length);
const previous = getPaddedString(bigStringLength, previousString);
const next = getPaddedString(bigStringLength, nextString);
console.log(previous, next);
let result = '';
let i;
for (i = 0; i < bigStringLength; i++) {
const previousChar = previous.charAt(i);
const nextChar = next.charAt(i);
// keep adding common characters
if (previousChar === nextChar) {
result += previousChar;
console.log(result, 'common character');
continue;
}
// when different characters are reached, try to add a character between these two
const charBetween = getCharBetween(previousChar, nextChar);
if (charBetween) {
result += charBetween;
console.log(result, 'character in-between. RETURNING');
// and you're done
return result;
}
// if there was no character between these two (their distance was exactly 1),
// repeat the low character, forget about the upper bound and just try to get bigger than lower bound
result += previousChar;
console.log(result, 'the lower character so we can forget about high bound');
i++;
break;
}
for (; previousString >= result; i++) {
const previousChar = previous.charAt(i);
const higherChar = getHigherAsciiChar(previousChar);
if (higherChar) {
// you found a digit that makes your result greater than the lower bound. You're done.
result += higherChar;
console.log(result, 'a higher character. RETURING');
return result;
}
// the digits are still very close, can't find a digit in-between (yet)
result += previousChar;
console.log(result, 'moving on to next digit');
}
// so you end up depleting all the character slots from the lower bound. Meh, just add any character.
result += getHigherAsciiChar(alphabet.charAt(0));
console.log(result, 'meh, just add any character. RETURNING');
return result;
}
function interleaveTest(order) {
const newOrder = [];
newOrder.push(getOrderString({ next: order[0] }));
for (let i = 0; i < order.length - 1; i++) {
newOrder.push(order[i]);
newOrder.push(getOrderString({ previous: order[i], next: order[i + 1] }));
}
newOrder.push(order[order.length - 1]);
newOrder.push(getOrderString({ previous: order[order.length - 1] }));
return newOrder;
}
let order = ['c'];
console.log('\n' + order.join(', ') + '\n');
order = interleaveTest(order);
console.log('\n' + order.join(', ') + '\n');
order = interleaveTest(order);
console.log('\n' + order.join(', ') + '\n');
order = interleaveTest(order);
console.log('\n' + order.join(', ') + '\n');
let atEnd = ['b'];
for (let i = 0; i < 10; i++) {
atEnd.push(getOrderString({ previous: atEnd[atEnd.length - 1] }));
}
console.log('\nat end: ' + atEnd.join(', ') + '\n');

How to divide multiple shapes in Paper.js like in Illustrator with Pathfinder

I have multiple overlapping squares in Paper.js, and I'd like to separate all the overlapping shapes into their own. You can do exactly this in Illustrator with the pathfinder divide. Before I attempt to just loop through all overlapping shapes and divide them with each other with what might have to be some nested loops I think, I'm wondering if there's a better way.
Example in Illustrator
I want to turn all these squares:
https://i.imgur.com/PPRi9M9.png
into pieces like this
https://i.imgur.com/xTFS8jP.png
(moved the pieces away from each other so you can see how they're separated)
I ended up going with my own solution which sounded more practical and simple than #arthur's answer. Not sure about which would be more performant though. To summarize, I map what blocks are overlapping with each other with a nested loop and Path.intersects(path), then do another nested loop to divide each block with its overlapping blocks with Path.divide(path) which will cut the original path with whatever path you're dividing it with.
Here's my actual code I'm using in my project with comments.
setupGrid() {
// Setup block row and column positions
for (let i = 0;i < this.total;i++) {
let x
let y
if (!odd(i)) {
x = firstColumnStartX + (this.size/2)
y = firstColumnStartY + ((i/2) * (this.size + this.gap)) + (this.size/2)
} else {
x = secondColumnStartX + (this.size/2)
y = secondColumnStartY + (Math.floor(i/2) * (this.size + this.gap)) + (this.size/2)
}
this.blocks.push(new paper.Path.Rectangle({
position: [x, y],
size: this.size,
strokeColor: '#ff000050'
}))
}
// Setup array to check what blocks are intersecting
const intersects = []
// Setup empty array with a nested array mapped to other blocks [5 x [5 x undefined]]
for (let i = 0;i < this.total;i++) {
intersects[i] = new Array(this.total).fill(undefined)
}
// Intersect checking
for (let i = 0;i < this.total;i++) {
const block = this.blocks[i]
for (let _i = 0;_i < this.total;_i++) {
const otherBlock = this.blocks[_i]
if (block !== otherBlock && intersects[i][_i] === undefined) {
intersects[_i][i] = intersects[i][_i] = block.intersects(otherBlock)
}
}
}
// First loop through all blocks
for (let i = 0;i < this.total;i++) {
let block = this.blocks[i]
// Then loop through other blocks only if they were intersected with the original block
for (let _i = 0;_i < this.total;_i++) {
const otherBlock = this.blocks[_i]
if (intersects[i][_i]) {
/* divide returns {
pieces: array of separated pieces that would be inside the original block's boundaries
leftoverBlock: what's leftover of the other block if the original block was subtracted from it
} */
const divide = this.divide(block, otherBlock)
block.remove()
otherBlock.remove()
// Override current block with the array of pieces
block = this.blocks[i] = divide.pieces
// Override other block with leftover
this.blocks[_i] = divide.leftoverBlock
// Don't let other block divide with original block since we already did it here
intersects[_i][i] = undefined
}
}
}
// Set random color for each piece to check if successful
for (let i = 0;i < this.blocks.length;i++) {
let block = this.blocks[i]
if (block instanceof Array) {
for (let _i = 0;_i < block.length;_i++) {
block[_i].fillColor = new paper.Color(Math.random(), Math.random(), Math.random(), 0.1)
}
} else {
block.fillColor = new paper.Color(Math.random(), Math.random(), Math.random(), 0.1)
}
}
}
// Divide blockA with blockB and expand
divideBlocks(blockA, blockB, pieces = []) {
const divideA = blockA.divide(blockB)
if (divideA instanceof paper.CompoundPath) {
for (let i = divideA.children.length;i--;) {
const child = divideA.children[i]
child.insertAbove(divideA)
pieces.push(child)
}
divideA.remove()
} else {
pieces.push(divideA)
}
return pieces
}
// Divide group (array of paths) with divider
divideGroup(children, divider, pieces = [], parent) {
for (let i = children.length;i--;) {
const child = children[i]
if (parent) {
child.insertAbove(parent)
}
if (child.intersects(divider)) {
this.divideBlocks(child, divider, pieces)
} else {
pieces.push(child)
}
}
}
// Subtract group (array of paths) from block
subtractGroupFromBlock(block, group) {
let oldBlock
let newBlock = block
for (let i = group.length;i--;) {
const child = group[i]
if (child.intersects(block)) {
newBlock = newBlock.subtract(child)
if (oldBlock) {
oldBlock.remove()
}
oldBlock = newBlock
}
}
return newBlock
}
// Check what kind of divide method to use
divide(blockA, blockB) {
const pieces = []
let leftoverBlock
if (blockA instanceof paper.Path) {
this.divideBlocks(blockA, blockB, pieces)
leftoverBlock = blockB.subtract(blockA)
} else if (blockA instanceof Array) {
this.divideGroup(blockA, blockB, pieces)
leftoverBlock = this.subtractGroupFromBlock(blockB, blockA)
}
return {
pieces,
leftoverBlock
}
}
My blocks set with random colors to differentiate each shape:
Overlapping blocks before:
https://i.imgur.com/j9ZSUC5.png
Overlapping blocks separated into pieces:
https://i.imgur.com/mc83IH6.png
Since you want to create shapes which did not exist before (according to your example, you want the operation to create the inner rectangle), I think you will have to loop over all overlapping shapes, compute intersections with Path.getIntersections(path[, include]), and re-create new paths from existing ones.
Once you computed all intersections, you will have to loop through all vertices, always rotating in the same direction, and create the new paths.
Take one (random) vertex, find the connected vertex "with the smallest angle" (it should work with currentVertex.getDirectedAngle(connectedVertex)) ; set the current vertex as visited and continue until you find the first vertex again. Create a shape, and redo this algorithm until you visited all vertices.
You could also use Path.intersect(path[, options]) but I don't think it would help you.

Unity - Making Camera Lock on To Enemy and stay behind player?

I am attempting to create a Camera that moves with the Player but locks onto an enemy when the player clicks the lock on button. The behaviour is almost working as I want it, the camera locks onto the target. And when the player stand in-front of the target it works fine. However as soon as the player runs past the target, the camera behaves strangely. It still looks at the Enemy, however it does not stay behind the player. Here is the code that dictates the behaviour:
if(MouseLock.MouseLocked && !lockedOn){ // MOUSE CONTROL:
Data.Azimuth += Input.GetAxis("Mouse X") * OrbitSpeed.x;
Data.Zenith += Input.GetAxis("Mouse Y") * OrbitSpeed.y;
} else if(lockedOn) { // LOCKON BEHAVIOUR:
FindClosestEnemy();
}
if (Target != null) {
lookAt += Target.transform.position;
base.Update ();
gameObject.transform.position += lookAt;
if(!lockedOn){
gameObject.transform.LookAt (lookAt);
} else if(enemyTarget != null) {
Vector3 pos1 = Target.transform.position ;
Vector3 pos2 = enemyTarget.transform.position ;
Vector3 dir = (pos2 - pos1).normalized ;
Vector3 perpDir = Vector3.Cross(dir, Vector3.right) ;
Vector3 midPoint = (pos1 + pos2) / 2f;
gameObject.transform.LookAt (midPoint);
}
}
And the Code for Finding the nearest Enemy:
void FindClosestEnemy () {
int numEnemies = 0;
var hitColliders = Physics.OverlapSphere(transform.position, lockOnRange);
foreach (var hit in hitColliders) {
if (!hit || hit.gameObject == this.gameObject || hit.gameObject.tag == this.gameObject.tag){
continue;
}
if(hit.tag != "Enemy") // IF NOT AN ENEMY: DONT LOCK ON
continue;
var relativePoint = Camera.main.transform.InverseTransformPoint(hit.transform.position);
if(relativePoint.z < 0){
continue;
}
numEnemies += 1;
if(enemyTarget == null){
print ("TARGET FOUND");
enemyTarget = hit;
}
}
if(numEnemies < 1){
lockedOn = false;
enemyTarget = null;
}
}
As I said, teh behaviour almost works as expected, however I need the camera to stay behind the player whilst locked on and it must face the enemy/midPoint between the enemy and player. How can this be done? Thank you for your time.
To clarify your intent: you want to lock the position relative to the target (player), whilst setting the camera rotation to look at either the target or a secondary target (enemy)? And your current code performs the rotation correctly but the positioning is buggy?
The easiest way to fix the camera relative to another object is to parent it in the scene. In your case you could add the camera as a child under the Player game object.
If you would rather not do this then look at your positioning code again:
lookAt += Target.transform.position;
base.Update ();
gameObject.transform.position += lookAt;
I don't know where lookAt comes from originally but to me this looks all wrong. Something called lookAt should have nothing to do with position and I doubt you want to += anything in the positioning code given that you want a fixed relative position. Try this instead:
public float followDistance; // class instance variable = distance back from target
public float followHeight; // class instance variable = camera height
...
if (Target != null) {
Vector3 newPos = target.position + (-Target.transform.forward * followDistance);
newPos.y += followHeight;
transform.position = newPos;
}
This should fix the positioning. Set the followDistance and followHeight to whatever you desire. Assuming your rotation code works this should fix the problem.

Media Foundation Frames in Byte Format Run-Time

I use media foundation to capture alive webcam video, is it possible to get the frames captured in a byte streams format in Run-Time and to write them as a stream of bits in a text file after each time cycle ?
I am not sure that I can have the stream in byte format(without container) neither I can do that on run time?
It's not completely clear what you're asking. If you want to capture the raw frames from webcam and save them to a file then the answer is yes that can be done. The Media Foundation SDK MFCaptureToFile sample does exactly that although because it uses a SinkWriter you will have to specify a container file type such as mp4 when creating it.
If you really do want to get the raw frames one by one then you need to dispense with the SinkWriter (or write a custom one). Below is a code snippet that shows getting samples from an IMFSourceReader and converting them into a byte array (and a few other things). You could write the byte array to a text file although unless you do something like put a bitmap header on it it won't be very useful. The IMFSourceReader, IMFMediaTypes all need to be set up correctly prior to being able to call ReadSample but hopefully it gives you a rough idea of where to look further.
HRESULT MFVideoSampler::GetSample(/* out */ array<Byte> ^% buffer)
{
if (_videoReader == NULL) {
return -1;
}
else {
IMFSample *videoSample = NULL;
DWORD streamIndex, flags;
LONGLONG llVideoTimeStamp;
// Initial read results in a null pSample??
CHECK_HR(_videoReader->ReadSample(
//MF_SOURCE_READER_ANY_STREAM, // Stream index.
MF_SOURCE_READER_FIRST_VIDEO_STREAM,
0, // Flags.
&streamIndex, // Receives the actual stream index.
&flags, // Receives status flags.
&llVideoTimeStamp, // Receives the time stamp.
&videoSample // Receives the sample or NULL.
), L"Error reading video sample.");
if (flags & MF_SOURCE_READERF_ENDOFSTREAM)
{
wprintf(L"\tEnd of stream\n");
}
if (flags & MF_SOURCE_READERF_NEWSTREAM)
{
wprintf(L"\tNew stream\n");
}
if (flags & MF_SOURCE_READERF_NATIVEMEDIATYPECHANGED)
{
wprintf(L"\tNative type changed\n");
}
if (flags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED)
{
wprintf(L"\tCurrent type changed\n");
IMFMediaType *videoType = NULL;
CHECK_HR(_videoReader->GetCurrentMediaType(
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
&videoType), L"Error retrieving current media type from first video stream.");
Console::WriteLine(GetMediaTypeDescription(videoType));
// Get the frame dimensions and stride
UINT32 nWidth, nHeight;
MFGetAttributeSize(videoType, MF_MT_FRAME_SIZE, &nWidth, &nHeight);
_width = nWidth;
_height = nHeight;
//LONG lFrameStride;
//videoType->GetUINT32(MF_MT_DEFAULT_STRIDE, (UINT32*)&lFrameStride);
videoType->Release();
}
if (flags & MF_SOURCE_READERF_STREAMTICK)
{
wprintf(L"\tStream tick\n");
}
if (!videoSample)
{
printf("Failed to get video sample from MF.\n");
}
else
{
DWORD nCurrBufferCount = 0;
CHECK_HR(videoSample->GetBufferCount(&nCurrBufferCount), L"Failed to get the buffer count from the video sample.\n");
IMFMediaBuffer * pMediaBuffer;
CHECK_HR(videoSample->ConvertToContiguousBuffer(&pMediaBuffer), L"Failed to extract the video sample into a raw buffer.\n");
DWORD nCurrLen = 0;
CHECK_HR(pMediaBuffer->GetCurrentLength(&nCurrLen), L"Failed to get the length of the raw buffer holding the video sample.\n");
byte *imgBuff;
DWORD buffCurrLen = 0;
DWORD buffMaxLen = 0;
pMediaBuffer->Lock(&imgBuff, &buffMaxLen, &buffCurrLen);
if (Stride != -1 && Stride < 0) {
// Bitmap needs to be flipped.
int bmpSize = buffCurrLen; // ToDo: Don't assume RGB/BGR 24.
int absStride = Stride * -1;
byte *flipBuf = new byte[bmpSize];
for (int row = 0; row < _height; row++) {
for (int col = 0; col < absStride; col += 3) {
flipBuf[row * absStride + col] = imgBuff[((_height - row - 1) * absStride) + col];
flipBuf[row * absStride + col + 1] = imgBuff[((_height - row - 1) * absStride) + col + 1];
flipBuf[row * absStride + col + 2] = imgBuff[((_height - row - 1) * absStride) + col + 2];
}
}
buffer = gcnew array<Byte>(buffCurrLen);
Marshal::Copy((IntPtr)flipBuf, buffer, 0, buffCurrLen);
delete flipBuf;
}
else {
buffer = gcnew array<Byte>(buffCurrLen);
Marshal::Copy((IntPtr)imgBuff, buffer, 0, buffCurrLen);
}
pMediaBuffer->Unlock();
pMediaBuffer->Release();
videoSample->Release();
return S_OK;
}
}
}

Can someone explain this bit manipulation code?

I have a tree control with checkboxes next to each node that allows for checked, unchecked and middle checked states on the nodes. When clicking a node, the parent and children are updated. The code I found that does the trick uses bit shifting and I'm trying to understand what exactly is happening.
Can someone explain the following code? Or even better, rewrite this code so it is easier to understand?
// click event handler
private function eventMessageTree_itemCheckHandler(event:TreeEvent):void {
var node:ITreeNode = ITreeNode(event.item);
var checkState:uint = TreecheckboxItemRenderer(event.itemRenderer).checkBox.checkState;
updateParents(node, checkState);
updateChilds(node, checkState);
}
private function updateChilds(item:ITreeNode, value:uint):void {
var middle:Boolean = (value & 2 << 1) == (2 << 1);
var selected:Boolean = (value & 1 << 1) == (1 << 1);
if (item.children.length > 0 && !middle) {
for each (var childNode:ITreeNode in item.children) {
childNode.checked = value == (1 << 1 | 2 << 1) ? "2" : value == (1 << 1) ? "1" : "0";
updateChilds(childNode, value);
}
}
}
private function updateParents(item:ITreeNode, value:uint): void {
var checkValue:String = (value == (1 << 1 | 2 << 1) ? "2" : value == (1 << 1) ? "1" : "0");
var parentNode:ITreeNode = item.parent;
if (parentNode) {
for each (var childNode:ITreeNode in parentNode.children) {
if (childNode.checked != checkValue) {
checkValue = "2";
}
}
parentNode.checked = checkValue;
updateParents(parentNode, value);
}
}
It looks like the checkState value in the control can be either 1, 2, or 4 (or possibly 0, 2, and 4?):
public static const CONTROL_UNCHECKED:uint = 1; // not checked, and some descendants are
public static const CONTROL_CHECKED:uint = 2; // checked, and all descendants are
public static const CONTROL_MIDDLE:uint = 4; // not checked, but some descendants are
while the checked value in the nodes can be either 0, 1, or 2:
public static const UNCHECKED:uint = 0; // not checked, and some descendants are
public static const CHECKED:uint = 1; // checked, and all descendants are
public static const MIDDLE:uint = 2; // not checked, but some descendants are
That's really confusing. Ideally these would be the same set of constants.
To update:
private function controlStateToNodeState(value:uint):uint {
return value / 2;
}
...
updateParents(node, controlStateToNodeState(checkState));
updateChilds(node, controlStateToNodeState(checkState));
...
/** Updates the descendants of the node based on state:
* If value is CHECKED, all children are CHECKED
* If value is UNCHECKED, all children are UNCHECKED
* If value is MIDDLE, children are left alone
*/
private function updateChilds(item:ITreeNode, value:uint):void {
if (value == MIDDLE) {
return; // if value is MIDDLE, children are left alone
}
// not middle, so update all children to my state
for each (var childNode:ITreeNode in item.children) {
childNode.checked = value;
updateChilds(childNode, value);
}
}
}
/**
* Updates the ancestor nodes based on state:
* If value is CHECKED, ancestors are made MIDDLE if not already checked
* If value is MIDDLE, ancestors are made middle (they should not already be CHECKED)
*/
private function updateParents(item:ITreeNode, value:uint): void {
...
}
Basically, an expression like this:
var middle:Boolean = (value & 2 << 1) == (2 << 1);
Is counter-intuitive. You usually test bits by shifting the constant 1 to the left, since that lets the number of bits shifted be the same as the index of the bit, counting the LSB (rightmost) bit as bit number 0.
Also, there's no point in testing the result with a == comparison, since it's always going to be either 0 or non-zero, so you can at least test for something simpler if your language requires that.
In C and C++, which by default interpret a non-zero integer as "true", the comparison is totally unnecessary and just serves to introduce clutter, repetition, and increase the risk of bugs.
I'd write this like so:
var middle:Boolean = (value & (1 << 2)) != 0;
The extra parenthesis should help make it clearer how things are grouped. Note how "2 << 1" was rewritten as "1 << 2". This is not just a "switch", you need to compute the proper shift to get the same bit value, 4 in this case.
Of course you could put the bit-testing into subroutines and call them, to make the code more readable.

Resources