As friends of mine who know me well knows that, I love all sorts of challenging games, especially those that requires lots of analytical thinking.
But the problem with this game is, it always needs 2 players to play.
There's the main reason I came up with the idea of making this into a program, so that I can play it anytime I want, alone.
Let's take a look at the program (attached at the bottom of this post).
At line 008, we need to generate a random set of number for a new game. In this program, we use numbers from 0-9 to represents different colors.
The whole subroutine for doing this is from line 138-146. For this program, we uses 10 possible colors, which are represented by number 0-9. If you wish to reduce the possible colors, feel free to change the number (9) in line 143 to your choice.
------
I'll skip the part where the program keeps looping seeking inputs from the player.
------
Let's go straight to the part where how the program gives reads the input from the players, and gives feedback on how many black/white seeds is earned each time. This part is a little bit tricky, which is at line 093-133.
If you look at subroutine &feedback_algo, we need to make sure that we need to handle all the possible seeds for the black one first, before we go for the white seeds.
And that's where line 104-112 comes into place.
From line 104-112, we can see that, the program loop 1 by 1 from each of the numbers, checking each number with its correct location. If they matches, then we masked the location out with a '-' symbol.
------
Now, for the white seeds, let's take a loot at lines 117-129.
For this part, we will need to loop thru the inputs from the users with all the left over numbers which are not marked with '-'.
Once a number from the inputs matches with any of the answer list, we need to marked them up with a '-' so that it doesn't get checked a 2nd time.
------
Well, that basically sums up everything.
And oh, yeah, if you are interested in playing it under your Window platform pc, here's the exe file, which I've compiled it.
Give it a try, and I hope you like it. I did :)
#!/apps/perl/bin/perl -w
system("clear");
&help_page();
my @code = &generate_code(); my $success = &play_mastermind();
# --------------------------------------------------------------- # feedback_algo # ============= # - After the CodeBreaker placed his guess, program will enter this look # and look for the appropriate feedback. # - BLACK == Correct number, correct location # - WRITE == correct number, wrong location # --------------------------------------------------------------- sub feedback_algo { my @guess = @_;
my $black = 0; ### Black == correct number at the correct location my $white = 0; ### White == correct number, but incorrect location
### Check for (CORRECT LOCATION + CORRECT NUMBER) ### - if found, remove it from @golden & @guess ### ---------------------------------------------------- my @golden = @code; for (my $i=0; $i<@golden; $i++) { if ( $golden[$i] =~ /$guess[$i]/ ) { $black++; $golden[$i] = '-'; $guess[$i] = '-'; } } # for
### Check for (CORRECT LOCATION + CORRECT NUMBER) ### - if found, remove it from @golden & @guess ### ---------------------------------------------------- for (my $i=0; $i<@golden; $i++) { for (my $x=0; $x<@guess; $x++) { if ( $guess[$x] ne '-' && $golden[$i] ne '-' && $guess[$x] =~ /$golden[$i]/ ) { $white++; $guess[$x] = '-'; $golden[$i] = '-'; next; } } # for } # for
return( $black, $white );
} # feedback
# ------------------------------ # Generate_code # ------------------------------ sub generate_code { my @retVal; for (my $i=0; $i<4; $i++) { push( @retVal, int(rand(9)) ); } # for return(@retVal); } # generate_code
# ------------------------------ # Show Score # ------------------------------ sub show_score_summary { my $file = shift(@_); my %user = ();
open($IN, $file) || die("Can't open score log file for read. Program Terminated!n"); while (!eof($IN)) { chomp( my $line = <$IN> ); my @tmp = split(' - ', $line); $tmp[2] = 13 if ($tmp[2] =~ /FAIL/); push( @{ $user{$tmp[0]}{scores} }, $tmp[2] ); } # while close($IN);
### Calculation for all data foreach my $name (keys(%user)) { my @tmp = sort {$a<=>$b} @{$user{$name}{scores}}; $user{$name}{fastest} = $tmp[0];
# ------------------------------ # Help Page # ------------------------------ sub help_page { print "---------------------------------------------------------------------n"; print "Master Mindn"; print "===========n"; print "1. Key in 4 digits, range from 0-9 in any particular order.n"; print "2. Feedback will be given every time after a user key in 4 digits.n"; print " - Black ==> Correct Number at the Correct Location.n"; print " - White ==> Correct Number at the Wrong Location.n"; print "n"; print "Show Scoren"; print "==========n"; print "%mastermind.pl -scoren"; print "---------------------------------------------------------------------n"; } # help_page
Posted by lionel319 @ Wed 30 Dec, 09, 09:44AM under Work
(Before we go into detail, here's how the data structure of my stock scanner. A brief peeping into the data structure might help in understanding what we'll be discussing in this post)
ADX is a very useful indicator.
In simple terms, it shows the strength of the counter whether it is trending, or in a trading range.
The higher the ADX number, the stronger the trend.
As most technicians put it, a reading which is higher than 20 means that the trend of this stock is very strong, and will be very likely the direction of the prior trend will continue.
Like any other indicators, it is not very wise to solely use only this indicator to determine an entry/exit point.
But, this indicator is undoubtedly the best tool that we can use on a stock scanning program to filter out all the strongly trending stocks, especially for one like me, who swing trades weak pullback on a strong trending stock.
Computing the ADX indicator filter is a bit complicated, not in the sense that it is ..... complicated, but it needs a lot of small calculation before we can fix things up, and come to the final ADX value.
Let's break things down into small little piece.
First, let's take a look at the ADX formula:-
whereby:- - N is the period - TR is the TRUE RANGE - t represents today - t-1 represents a day before today, (yesterday)
As the TR has been discussed and explain in my earlier post on "ATR Indicator In Perl", we won't be discussing about it here anymore. (yes, TR is exactly the Average True Range. They are the same thing).
Directional Move Indicator
This is the first component that we need to calculate.
We first need to find the Delta High/Low of today, and the formula for that is
Delta High = today's high - yesterday's high
Delta Low = yesterday's low - today's low
if Delta High==0 && Delta Low==0, or Delta High==Delta Low,
DM+ = 0, DM- = 0
If Delta High > Delta Low
DM+ = Delta High, DM- = 0
If Delta Low > Delta High
DM- = Delta Low, DM+ = 0
(See source code below, line 05->26)
Average Directional Move Indicator
Now, we need to calculate the Average DM+ and DM-.
Still remember the ATR post? Yes. The average formula used is the same that is used for the ATR, and here it is:-
(*Note:- we will just use a simple average calculation for the first ADM+/ADM- value, as we do not have a previous ADM(t-1) to look back. Eg:- it will be just ADM+/N or ADM-/N [see source code below, line 31->42]
For the rest of the ADM calculation, we will stick with the above formula [see source code below, line 43->47 ] )
Directional Index
Here's the formula for it:-
Basically, what this does is just to make it into a percentage point, so that the values will be of the range from 0-100.
(see source code below, line 52 & 53)
Directional Movement Index
This will be the formula for the Directional Movement Index, which is being coded from line 55->59.
Average Directional Movement Index
We've finally reached the final step of calculating the ADX(Average Directional Movement Index).
Looking back at the first formula shown above:-
The same thing happens here as with the ADM+/ADM- calculation.
As when calculating for the the first day's ADX, we do not have a previous day's value, thus, we will be using a simple average calculation to reach to the ADX value for the first day:-
ADX(first day) == total of previous N period's DX / N period
(see source code below, line 67->82)
The rest of the ADX values are being computed using the original formula (see source code below, line 83->86)
Well, That's all about it.
We have now covered on a stock scanner which will be able to filter for stocks based on their
Before we go into detail about ADX, we must first get ATR up, which is one of the component used in calculating the ADX.
Let's start.
Before we get into the details of Average True Range(ATR), we first must understand how Wilder defines in finding the TR(True Range).
Wilder started with a concept called True Range (TR) which is defined as the greatest of the following:
The current High less the current Low.
The absolute value of the current High less the previous Close.
The absolute value of the current Low less the previous Close.
If you still follow me on my way of defining the object data structure for my stocks filtering program, we can therefore achieve the above TR calculation by the below attached code, from line 11-19.
Once we've had the True Range(TR) data attached to each individual date, we can now settle in finding their respective ATR.
There is a little tricky part here.
Here's the formula that the ATR uses:-
ATR(n) = [ATR(yesterday) * (n-1)] + TR / n
whereby:- n == look back period TR == today's True Range ATR(yesterday) == yesterday's ATR value
The tricky part with this is that since we are going to have to look back at the previous day's ATR value to calculate today's ATR, thus, we need to work from the old days up to the current day.
Another thing is that, how are we gonna work out for the ATR value for the first day, since there's no previous day's ATR value to look for?
Well, for this case, we're gonna use a simple average form to calculate for the first day's ATR value, which is why the if loop from line 28-31.
And well, that pretty much sums up the rest of the code, whereby line 21-36 is the loop that calculates the ATR value for each day.
# --------------------------------------------- sub calculateAverateTrueRange { # The True Range is defined as the highest of the following numbers: # * The absolute value of the difference between the current bar High and the current bar Low. # * The absolute value of the difference between the current bar High and the previous bar Close. # * The absolute value of the difference between the current bar Low and the previous bar Close.
my ($D, $period) = @_;
for (my $i=0; $i<@$D-2; $i++) { my $r1 = abs( $D->[$i]{High} - $D->[$i]{Low} ); my $r2 = abs( $D->[$i]{High} - $D->[$i+1]{Close} ); my $r3 = abs( $D->[$i]{Low} - $D->[$i+1]{Close} );
my @tmp = sort {$a<=>$b} ($r1, $r2, $r3); $D->[$i]{TR} = $tmp[2]; } # for
for (my $i=@$D-$period-2; $i>=0; $i--) { my $total = 0; for (my $x=0; $x<$period; $x++) { $total = $total + $D->[$i+$x]{TR}; } if ($i==@$D-$period-2) { # first ATR $D->[$i]{"ATR_$period"} = $total / $period; } else { $D->[$i]{"ATR_$period"} = ( ($D->[$i+1]{"ATR_$period"}*($period-1)) + $D->[$i]{TR} ) / $period; } } # for
As always, before anything is done, a proper planning on how you design your data structure will decide how efficient the entire flow will be. (read here about the importance of data structure planning)
But, before we can know what kind of data structure suits your program, we need to know how our program flow looks like.
Having a rough idea on how your overall program flow looks like, you will then be able to recognize how most likely that you are going to access your data, and thus, help you in designing a better data structure.
With that in mind, let's first go thru on the program flow of our Sudoku Solver.
The above flow chart shorts the overall program flow of our sudoku solver.
A brute force algorithm is a method which finds the best solution by enumerating all possible values.
The disadvantage of this method is that, if the database is too huge, it will never be able to finish the task. (for example, finding the best next-move for a chess play)
but since we know that our task is comparatively very small, it won't be taking up too much resource and time. Furthermore, it gets the task done pretty fast and easy. :)
1. Find all the empty boxes.
2. Run thru the empty boxes one by one, solving one box at a time.
3. Find a possible number that can be placed in that box, starting with 1, to the maximum number of 9.
4. Go back to step #2 if the number is found.
5. but if non of the numbers from 1-9 is possible for this box, that means 2 things:-
- either one of the previous box's solution is wrong. - this puzzle is totally unsolvable.
6. Let the program run until the end.
7. and if it stops at the final empty box with a valid number (1-9), then you've got the answer for this puzzle.
8. else, you know that, this puzzle is an unsolvable puzzle.
Here's the final decision on the data structure that we'll be using in this program:-
Now, let's go thru some of the interesting part of the program. Won't be going thru everything, as some of it is quite straight forward.
### Assuming that we have already got the data of the ### sudoku puzzle stored in an array , @b ### whereby, $b[3][5] is representing the box of Row 3, Column 5. ### Boxes which are empty will be given with a '0' value. ### -------------------------------------------------------- ### We loop thru all the rows/columns of the boxes, ### and grep all the boxes locations which are empty ### and store the value as a string into a variable call @unfixed ### Assuming we have empty boxes in row3/column5 and ### row5/column7, thus, this is what we expect to see:- ### @unfixed = ( '35', '57' ); ### --------------------------------------------------------
### This is basically the entire main program flow already. ### As stated in the above blog flowchart diagram. my $x = 0; while ( $x < @$unfixed ) { my ($r, $c) = split( //, $unfixed->[$x] ); my $num = possible_number( $b[$r][$c] , $r, $c);
$b[$r][$c] = $num; if ($num==0) { $x--; if ($x<0) { print "Unsolvable puzzle ..... you "; print "STUPID @$$ !!!"; die(); } #if } else { $x++; }
} #foreach
### Print out the answer of the data structure @b, ### in a sudoku like format. &print_answer;
# --- The End ---
Now , there's something which needs more attention in the above program flow.
Notice the call in line 07 on the function &possible_numbers()?
That function consists of 3 sub smaller functions.
First, we know that, in order for a number to be considered as a possible numbers for an empty box, it first needs to meet 3 criteria:-
- The number must not exists in the same row. - The number must not exists in the same column. - The number must not exists in the same block.
Thus, we have these 3 sub routines, - is_number_exists_in_block - is_number_exists_in_row - is_number_exists_in_col
If the numbers doesn't exist in the same row+col+block, then this number ($num) can now be considered as the possible answers.
Here is how the actual script looks like...
# Solving sub possible_number { my ($num, $r, $c) = @_; $num=$num+1 if ($num==0);
while ($num<10) { if (! is_number_exist($num, $r, $c) ) { return($num); } #if $num++; } #while return(0); } #possible_number #------------------------------------------------------------------- sub is_number_exist { my ($num, $r, $c) = @_; return(1) if ( is_number_exist_in_block(@_) || is_number_exist_in_row(@_) || is_number_exist_in_col(@_) ); return(0); } #is_number_exist #------------------------------------------------------------------- sub is_number_exist_in_block { my ($num, $r, $c) = @_;
my $starting_row = int($r/3) * 3; my $starting_col = int($c/3) * 3;
for (my $row=$starting_row; $row<$starting_row+3; $row++) { for (my $col=$starting_col; $col<$starting_col+3; $col++) { return(1) if ( $num == $b[$row][$col] ); } } #for return(0); } #is_number_exist_in_block #------------------------------------------------------------------- sub is_number_exist_in_row { my ($num, $row, $col) = @_;
for ($col=0; $col<9; $col++) { return(1) if ( $num == $b[$row][$col] ); } return(0); } #is_number_exist_in_row #------------------------------------------------------------------- sub is_number_exist_in_col { my ($num, $row, $col) = @_;
for ($row=0; $row<9; $row++) { return(1) if ( $num == $b[$row][$col] ); } return(0); } #is_number_exist_in_col #-------------------------------------------------------------------
That's all that I have.
If you are interested in the actual source code, you can get it here.
Games which require lots of thinking interest me the most.
...... and one of them is undoubtedly, the famous sudoku.
My lunch time normally spans across 1:30 hour, and if I were to eat in, 30 minutes is pretty much the most it takes for me to gobble down my food, leaving a whole lot of time for me to fiddle around.
Most of the time, websudoku will be the my favourite.
An evil level of sudoku usually takes me around 20-40 minutes, depending on the luck, the alignment of the sun, the stars and the moon, and my mood of that day.
And because it's my passion, I came out with a simple script that solves this puzzle, in perl. It was an old program from me, (written like 7 years ago?) just that I recently only for some unknown reason decided to share it.
Anyway, here's the exe file, which is Windows based. Enjoy
If you are interested in how it was done, and the actual source code, go here.
^_^
=====================================
tag : program, programming, perl, sudoku, solver, puzzle, board, game