LZ77 compression of palmdoc - compression

I am trying to create a utility for generating palmdoc/mobipocket format ebook files, it is said that mobi uses LZ77 compression technique to compress their records, but I found that there is quite a deviation from standard LZ77, My main source of reference is Calibre ebook creator with C implementation for palmdoc
In this file, uncompress works well, but I have not been able to compress a mobi record identically similar either using other implementation or this (Calibre code doent decompress the same).
I found some differences like, (<-- my comments follow in code)
static Py_ssize_t <-- can replaced with size_t
cpalmdoc_do_compress(buffer *b, char *output) {
Py_ssize_t i = 0, j, chunk_len, dist;
unsigned int compound;
Byte c, n;
bool found;
char *head;
buffer temp;
head = output;
temp.data = (Byte *)PyMem_Malloc(sizeof(Byte)*8); temp.len = 0;
if (temp.data == NULL) return 0;
while (i < b->len) {
c = b->data[i];
//do repeats
if ( i > 10 && (b->len - i) > 10) { <-- ignores any match outside this range
found = false;
for (chunk_len = 10; chunk_len > 2; chunk_len--) {
j = cpalmdoc_rfind(b->data, i, chunk_len);
dist = i - j;
if (j < i && dist <= 2047) { <-- 2048 window size instead of 4096
found = true;
compound = (unsigned int)((dist << 3) + chunk_len-3);
*(output++) = CHAR(0x80 + (compound >> 8 ));
*(output++) = CHAR(compound & 0xFF);
i += chunk_len;
break;
}
}
if (found) continue;
}
//write single character
i++;
if (c == 32 && i < b->len) { <-- if space is encountered skip char & check for next sequence for match otherwise do this, due to this code had wrong result.
n = b->data[i];
if ( n >= 0x40 && n <= 0x7F) {
*(output++) = CHAR(n^0x80); i++; continue;
}
}
if (c == 0 || (c > 8 && c < 0x80))
*(output++) = CHAR(c);
else { // Write binary data <-- why binary data? LZ is for text encoding
j = i;
temp.data[0] = c; temp.len = 1;
while (j < b->len && temp.len < 8) {
c = b->data[j];
if (c == 0 || (c > 8 && c < 0x80)) break;
temp.data[temp.len++] = c; j++;
}
i += temp.len - 1;
*(output++) = (char)temp.len;
for (j=0; j < temp.len; j++) *(output++) = (char)temp.data[j];
}
}
PyMem_Free(temp.data);
return output - head;
}
is this implementation correct?

PalmDoc compression essentially is byte pair compression, i.e. a variant of LZ77

Related

returning str vs. str.substr(0,str.size()) gives me different outputs in leetcode

I just solved https://leetcode.com/problems/push-dominoes/.
My code is
class Solution {
public:
string pushDominoes(string dominoes) {
string res(dominoes.size(), ' ');
dominoes = 'L' + dominoes + 'R';
int l = 0;
for(int r = 1; r < dominoes.size(); r++)
{
if(dominoes[r] != '.')
{
//check which case we have [L...R], [R....L] [L.....L] [R...R]
if(l != 0) res[l-1] = dominoes[l];
if(r != dominoes.size() - 1) res[r-1] = dominoes[r];
if(dominoes[l] == dominoes[r])
{
for(int i = l; i <= r; i++)
if(i > 0) res[i-1] = dominoes[r];
}
else if(dominoes[l] == 'L' && dominoes[r] == 'R')
for(int i = l+1; i < r; i++) res[i-1] = '.';
else if(dominoes[l] == 'R' && dominoes[r] == 'L')
{
if((l+r)%2 == 0)
{
auto mid = (l+r)/2;
res[mid-1] = '.';
for(int i = l+1; i < mid; i++) res[i-1] = 'R';
for(int i = mid+1; i < r; i++) res[i-1] = 'L';
}
else
{
auto mid = (l+r)/2;
for(int i = l+1; i <= mid; i++) res[i-1] = 'R';
for(int i = mid+1; i < r; i++) res[i-1] = 'L';
}
}
l = r;
}
}
return res;
// return res.substr(0, res.size());
}
};
int main( )
{
Solution soln;
cout << soln.pushDominoes(".L.R.") << endl;
}
For one of the test cases, where the input is ".L.R." leetcode states that my output is "LL.RRRLLRRLL.." when I use return res. The answer is supposed to be "LL.RR". But I printed out res and it's indeed "LL.RR" and size 5.
If I change my code to return return res.substr(0, res.size()) instead of return res, I get the correct solution. I'm perplexed as to why this is occuring when these 2 return statements should be identical?
I also ported the code over and compiled it on my computer, and I get the correct solution. This makes me wonder if there's some differences in how my code is handled between different C++ compilers, or if Leetcode might have some issues for this particular problem. Any advice?
string res(dominoes.size(), ' ');
Sets the size of res to that of dominoes and this size is never changed. res can never be larger than dominoes. With input of ".L.R.", dominoes's length is 5 . res is length 5.
dominoes = 'L' + dominoes + 'R';
Changes the size of dominoes to 7.
for (int r = 1; r < dominoes.size(); r++)
iterates r from 1 to 6.
Aside: When ever I see a <= in a for loop I stop for a longer look. It's wrong far more often than it is right.
In this case it seems to be only sort of wrong, allowing
for (int i = l; i <= r; i++)
if (i > 0) res[i - 1] = dominoes[r];
to iterate i from l to r. Since r can be 6, i can be 6. That means
if (6 > 0) res[6 - 1] = dominoes[6];
is possible. This resolves to
res[5] = dominoes[6];
and res[5] is not valid. Writing there invokes undefined behaviour, and in this case it appears to overwrite the null terminator of the c-style string backing the string.
Another aside: Avoid using l as a variable name. It looks too much like 1 and leads to bugs or misreading of the code.
To fix, you could expand the logic of if (i > 0) into if (i > 0 && i-1 < res.size()) to filter out this problem, but you're better off reworking or replacing the algorithm so that you never find yourself in this situation.

Convert string to float or integer without using built in functions (like atoi or atof)

I'm new to C++ and our teacher asked us to get a function that does the above title. So far I've got a function that converts a string to an integer, but I have no idea about how to modify it to make it work if the numbers in the string would represent a float.
int convert(char str[], int size) {
int number = 0;
for (int i = 0; i < size; ++i) {
number += (str[i] - 48)*pow(10, (size - i - 1));
}
return number;
}
If I run:
char myString[] = "12345";
convert(myString, 5);
I get:
12345
But if I run:
char myString[] = "123.45";
convert(myString, 5);
I get:
122845
How could I modify my program to work with floats too? I know convert function is meant to return an int so, should I use two more functions?
I was thinking about one that determinates if the string is inteded to be converted to an integer or a string, and the other that'll actually convert the string to a float.
Here is the function for doing so...
template<class T, class S>
T convert_string_to_number(S s)
{
auto result = T(0.l);
if (s.back() == L'F' || s.back() == L'f')
s = s.substr(0u, s.size() - 1u);
auto temp = s;
auto should_add = false;
if (!std::is_floating_point<T>::value)
{
should_add = temp.at(temp.find_first_of(L'.') + 1) >= '5';
temp.erase(temp.begin() + temp.find_first_of(L'.'), temp.end());
}
else if (temp.find_first_of(L'.') != S::npos)
temp.erase(temp.begin() + temp.find_first_of(L'.'));
for (int i = temp.size() - 1u; i >= 0; --i)
if (temp[i] >= L'0' && temp[i] <= L'9')
result += T(std::powl(10.l, temp.size() - i - 1.l) * (temp[i] - L'0'));
else
throw std::invalid_argument("Invalid numerical string!");
if (s.find(L'-') != S::npos)
result = -T(std::fabs(result));
if (s.find(L'.') != S::npos && std::is_floating_point<T>::value)
result /= T(std::powl(10.l, s.size() - s.find(L'.') - 1.l));
return std::is_floating_point<T>::value ? T(result) : T(result + T(should_add));
}
Just use it like you typically would...
auto some_number = convert_string_to_number<float>(myString);...
For the floating point part of the assignment: what about regular expressions? It is also kind of built-in functionality, but general purpose, not designed for your particular task, so I hope your teacher will be fine with this idea.
You can use the following regex: [+-]?([0-9]*[.])?[0-9]+ (I got it from this answer) to detect if provided string is a floating point number. Then you can modify the expression a little bit to capture the +/- signs and parts before/after the dot separator. Once you extract these features the task should be relatively simple.
Also please change your method signature to: float convert(const std::string& str).
Try this :
int convert(char str[], int size) {
int number = 0;
for (int i = 0; i < size; ++i) {
number += (str[i] - 48)*pow(10, (size - i - 1));
}
return number;
}
int pow10(int radix)
{
int r = 1;
for (int i = 0; i < radix; i++)
r *= 10;
return r;
}
float convert2float(char str[], int size) { //size =6
// convert to string_without_decimal
char str_without_decimal[10];
int c = 0;
for (int i = 0; i < size; i++)
{
if (str[i] >= 48 && str[i] <= 57) {
str_without_decimal[c] = str[i];
c++;
}
}
str_without_decimal[c] = '\0'; //str_without_decimal = "12345"
//adjust size if dot present or not. If no dot present => size = c
size = (size != c ?) size - 1 : size; //size = 5 = 6-1 since dot is present
//convert to decimal
int decimal = convert(str_without_decimal, size); //decimal = 12345
//get divisor
int i;
for (i = size; i >= 0; i--) {
if (str[i] == '.') break;
}
int divisor = pow10(size - i); //divisor = 10;
return (float)decimal/(float) divisor; // result = 12345 /10
}
int main()
{
char str[] = "1234.5";
float f = convert2float(str, 6);
cout << f << endl;
return 0;
}

c++ Decimal to binary, then use operation, then back to decimal

I have an array with x numbers: sets[ ](long numbers) and a char array operations[ ] with x-1 numbers. For each number from sets[ ], its binary form(in 64bits) would be the same as a set of numbers( these numbers being from 0 to 63 ), 1's and 0's representing whether it is inside a subset or not ( 1 2 4 would be 1 1 0 1, since 3 is missing)
ex: decimal 5 --->000...00101 , meaning that this subset will only have those 2 last numbers inside it(#63 and #61)
now,using the chars i get in operations[], i should work with them and the binaries of these numbers as if they were operations on subsets(i hope subset is the right word), these operations being :
U = reunion ---> 101 U 010 = 111
A = intersection ---> 101 A 001 = 001
\ = A - B ---> 1110 - 0011 = 1100
/ = B-A ---> like the previous one
so basically I'd have to read numbers, make them binary, use them as if they were sets and use operations accordingly, then return the result of all these operations on them.
my code :
include <iostream>
using namespace std;
void makeBinaryVector(int vec[64], long xx)
{
// put xx in binary form in array "vec[]"
int k = 63;
long x = xx;
if(xx == 0)
for(int i=0;i<64;i++)
vec[i] = 0;
while(x != 0)
{
vec[k] = x % 2;
x = x / 2;
k--;
}
}
void OperationInA(int A[64], char op, int B[64])
{
int i;
if(op == 'U') //reunion
for(i=0;i<64;i++)
if(B[i] == 1)
A[i] = 1;
if(op == 'A') //intersection
for(i=0;i<64;i++)
{
if((B[i] == 1) && (A[i] == 1))
A[i] = 1;
else
A[i] = 0;
}
if(op == '\\') //A-B
for(i=0;i<64;i++)
{
if( (A[i] == 0 && B[i] == 0) || (A[i] == 0 && B[i] == 1) )
A[i] = 0;
else
if((A[i] == 1) && (B[i] == 1))
A[i] = 0;
else
if((A[i] == 1) && (B[i] == 0))
A[i] = 1;
}
if(op == '/') //B-A
for(i=0;i<64;i++)
{
if(B[i] == 0)
A[i] = 0;
else
if((B[i] == 1) && (A[i] == 0))
A[i] = 1;
else
if((B[i] == 1) && (A[i] == 1))
A[i] = 0;
}
}
unsigned long setOperations(long sets[], char operations[], unsigned int x)
{
unsigned int i = 1; //not 0, since i'll be reading the 1st number separately
unsigned int j = 0;
unsigned int n = x;
int t;
long a = sets[0];
int A[64];
for(t=0;t<64;t++)
A[t] = 0;
makeBinaryVector(A, a); //hold in A the first number, binary, and the results of operations
long b;
int B[64];
for(t=0;t<64;t++) //Hold the next number in B[], in binary form
B[t] = 0;
char op;
while(i < x && j < (x-1) )
{
b = sets[i];
makeBinaryVector(B, b);
op = operations[j];
OperationInA(A, op, B);
i++; j++;
}
//make array A a decimal number
unsigned int base = 1;
long nr = 0;
for(t=63; t>=0; t--)
{
nr = nr + A[t] * base;
base = base * 2;
}
return nr;
}
long sets[100];
char operations[100];
long n,i;
int main()
{
cin>>n;
for(i=0;i<n;i++)
cin>>sets[i];
for(i=0;i<n-1;i++)
cin>>operations[i];
cout<<setOperations(sets,operations,n);
return 0;
}
So everything seems fine, except when im trying this :
sets = {5, 2, 1}
operations = {'U' , '\'}
5 U 2 is 7(111), and 7 \ 1 is 6 (111 - 001 = 110 --> 6)
the result should be 6, however when i Input them like that the result is 4 (??)
however, if i simply input {7,1} and { \ } the result is 6,as it should be. but if i input them like i first mentioned {5,2,1} and {U,} then its gonna output 4.
I can't seem to understand or see what im doing wrong...
You don't have to "convert to binary numbers".
There's no such thing as 'binary numbers'. You can just perform the operations on the variables.
For the reunion, you can use the bitwise OR operator '|', and for the intersection, you can use the bitwise AND operator '&'.
Something like this:
if (op == 'A')
result = a & b;
else if (op == 'U')
result = a | b;
else if (op == '\\')
result = a - b;
else if (op == '/')
result = b - a;
Use bitwise operators on integers as shown in #Hugal31's answer.
Note that integer size is usually 32bit, not 64bit. On a 64bit system you need long long for 64bit integer. Use sizeof operator to check. int is 4 bytes (32bit) and long long is 8 bytes (64bit).
For the purpose of homework etc., your conversion to vector cannot be right. You should test it to see if it outputs the correct result. Otherwise use this:
void makebinary(int vec[32], int x)
{
int bitmask = 1;
for (int i = 31; i >= 0; i--)
{
vec[i] = (x & bitmask) ? 1 : 0;
bitmask <<= 1;
}
}
Note the use of shift operators. To AND the numbers you can do something like the following:
int vx[32];
int vy[32];
makebinary(vx, x);
makebinary(vy, y);
int result = 0;
int j = 1;
for (int i = 31; i >= 0; i--)
{
int n = (vx[i] & vy[i]) ? 1 : 0;
result += n * j;
j <<= 1;
}
This is of course pointless because you can just say int result = X & Y;

TGA RLE compressing ( writing ) algorithm on c++

I found this code and I wonder why it does't works ..
// f is an stream
void writeTGA(ostream& f, bool rle) const{
f<<ubyte(0);//ID length
f<<ubyte(0);//Color map Type
if(rle)
f<<ubyte(10);//Image Type Code
else
f<<ubyte(2);//Image Type Code
f<<ubyte(0);//Color map stuff
f<<ubyte(0);
f<<ubyte(0);
f<<ubyte(0);
f<<ubyte(0);
f<<ubyte(0);//X Origin of Image
f<<ubyte(0);//X Origin of Image
f<<ubyte(0);//Y Origin of Image
f<<ubyte(0);//Y Origin of Image
f << ubyte(width%256);
f << ubyte(width>>8);
f << ubyte(height%256);
f << ubyte(height>>8);
f<<ubyte(24);//Image Pixels Size
f<<ubyte(32);//Image Descriptor Byte
if(rle)
{
cout<<"rleHeadHead"<<endl;
ubyte rleHead = 0;
ubyte diff[128];
int i = 0;
while(i<width*height)
{
rleHead = 1;
/* RLE */
if(i+1 < width*height)
while(pixels[i] == pixels[i+1]) {
if(i+1 >= width*height || rleHead >= 128)
break;
rleHead++;
i++;
}
if(rleHead > 1)
{
f<< (rleHead+127);
f<<pixels[i].b;
f<<pixels[i].g;
f<<pixels[i].r;
}
rleHead = 0;
/* RAW */
if(i+1 < width*height)
{
while(pixels[i+rleHead] != pixels[i+rleHead+1])
{
if( (i+rleHead+1) >= width*height || rleHead >= 128)
break;
rleHead++;
}
} else
rleHead++;
if(rleHead > 0)
{
f << (rleHead-1);
for(int j = 0; j < rleHead; j++)
{
diff[j] = pixels[i+j].b;
diff[j] = pixels[i+j].g;
diff[j] = pixels[i+j].r;
}
f.write((const char*) diff, rle*3);
i += rleHead;
}}}
else{
for(int i = 0 ; i < width*height ; i++){
f<< pixels[i].b;
f<< pixels[i].g;
f<< pixels[i].r;}
}
}
I tried to implement it and it seems not good ..
Otherwise, someone know if it exist a library or just a simple file where I can find this algorithm ?
Thanks you in advance

Placing random numbers in a grid

I need to place numbers within a grid such that it doesn't collide with each other. This number placement should be random and can be horizontal or vertical. The numbers basically indicate the locations of the ships. So the points for the ships should be together and need to be random and should not collide.
I have tried it:
int main()
{
srand(time(NULL));
int Grid[64];
int battleShips;
bool battleShipFilled;
for(int i = 0; i < 64; i++)
Grid[i]=0;
for(int i = 1; i <= 5; i++)
{
battleShips = 1;
while(battleShips != 5)
{
int horizontal = rand()%2;
if(horizontal == 0)
{
battleShipFilled = false;
while(!battleShipFilled)
{
int row = rand()%8;
int column = rand()%8;
while(Grid[(row)*8+(column)] == 1)
{
row = rand()%8;
column = rand()%8;
}
int j = 0;
if(i == 1) j= (i+1);
else j= i;
for(int k = -j/2; k <= j/2; k++)
{
int numberOfCorrectLocation = 0;
while(numberOfCorrectLocation != j)
{
if(row+k> 0 && row+k<8)
{
if(Grid[(row+k)*8+(column)] == 1) break;
numberOfCorrectLocation++;
}
}
if(numberOfCorrectLocation !=i) break;
}
for(int k = -j/2; k <= j/2; k++)
Grid[(row+k)*8+(column)] = 1;
battleShipFilled = true;
}
battleShips++;
}
else
{
battleShipFilled = false;
while(!battleShipFilled)
{
int row = rand()%8;
int column = rand()%8;
while(Grid[(row)*8+(column)] == 1)
{
row = rand()%8;
column = rand()%8;
}
int j = 0;
if(i == 1) j= (i+1);
else j= i;
for(int k = -j/2; k <= j/2; k++)
{
int numberOfCorrectLocation = 0;
while(numberOfCorrectLocation != i)
{
if(row+k> 0 && row+k<8)
{
if(Grid[(row)*8+(column+k)] == 1) break;
numberOfCorrectLocation++;
}
}
if(numberOfCorrectLocation !=i) break;
}
for(int k = -j/2; k <= j/2; k++)
Grid[(row)*8+(column+k)] = 1;
battleShipFilled = true;
}
battleShips++;
}
}
}
}
But the code i have written is not able to generate the numbers randomly in the 8x8 grid.
Need some guidance on how to solve this. If there is any better way of doing it, please tell me...
How it should look:
What My code is doing:
Basically, I am placing 5 ships, each of different size on a grid. For each, I check whether I want to place it horizontally or vertically randomly. After that, I check whether the surrounding is filled up or not. If not, I place them there. Or I repeat the process.
Important Point: I need to use just while, for loops..
You are much better of using recursion for that problem. This will give your algorithm unwind possibility. What I mean is that you can deploy each ship and place next part at random end of the ship, then check the new placed ship part has adjacent tiles empty and progress to the next one. if it happens that its touches another ship it will due to recursive nature it will remove the placed tile and try on the other end. If the position of the ship is not valid it should place the ship in different place and start over.
I have used this solution in a word search game, where the board had to be populated with words to look for. Worked perfect.
This is a code from my word search game:
bool generate ( std::string word, BuzzLevel &level, CCPoint position, std::vector<CCPoint> &placed, CCSize lSize )
{
std::string cPiece;
if ( word.size() == 0 ) return true;
if ( !level.inBounds ( position ) ) return false;
cPiece += level.getPiece(position)->getLetter();
int l = cPiece.size();
if ( (cPiece != " ") && (word[0] != cPiece[0]) ) return false;
if ( pointInVec (position, placed) ) return false;
if ( position.x >= lSize.width || position.y >= lSize.height || position.x < 0 || position.y < 0 ) return false;
placed.push_back(position);
bool used[6];
for ( int t = 0; t < 6; t++ ) used[t] = false;
int adj;
while ( (adj = HexCoord::getRandomAdjacentUnique(used)) != -1 )
{
CCPoint nextPosition = HexCoord::getAdjacentGridPositionInDirection((eDirection) adj, position);
if ( generate ( word.substr(1, word.size()), level, nextPosition, placed, lSize ) ) return true;
}
placed.pop_back();
return false;
}
CCPoint getRandPoint ( CCSize size )
{
return CCPoint ( rand() % (int)size.width, rand() % (int)size.height);
}
void generateWholeLevel ( BuzzLevel &level,
blockInfo* info,
const CCSize &levelSize,
vector<CCLabelBMFont*> wordList
)
{
for ( vector<CCLabelBMFont*>::iterator iter = wordList.begin();
iter != wordList.end(); iter++ )
{
std::string cWord = (*iter)->getString();
// CCLog("Curront word %s", cWord.c_str() );
vector<CCPoint> wordPositions;
int iterations = 0;
while ( true )
{
iterations++;
//CCLog("iteration %i", iterations );
CCPoint cPoint = getRandPoint(levelSize);
if ( generate (cWord, level, cPoint, wordPositions, levelSize ) )
{
//Place pieces here
for ( int t = 0; t < cWord.size(); t++ )
{
level.getPiece(wordPositions[t])->addLetter(cWord[t]);
}
break;
}
if ( iterations > 1500 )
{
level.clear();
generateWholeLevel(level, info, levelSize, wordList);
return;
}
}
}
}
I might add that shaped used in the game was a honeycomb. Letter could wind in any direction, so the code above is way more complex then what you are looking for I guess, but will provide a starting point.
I will provide something more suitable when I get back home as I don't have enough time now.
I can see a potential infinite loop in your code
int j = 0;
if(i == 1) j= (i+1);
else j= i;
for(int k = -j/2; k <= j/2; k++)
{
int numberOfCorrectLocation = 0;
while(numberOfCorrectLocation != i)
{
if(row+k> 0 && row+k<8)
{
if(Grid[(row)*8+(column+k)] == 1) break;
numberOfCorrectLocation++;
}
}
if(numberOfCorrectLocation !=i) break;
}
Here, nothing prevents row from being 0, as it was assignd rand%8 earlier, and k can be assigned a negative value (since j can be positive). Once that happens nothing will end the while loop.
Also, I would recommend re-approaching this problem in a more object oriented way (or at the very least breaking up the code in main() into multiple, shorter functions). Personally I found the code a little difficult to follow.
A very quick and probably buggy example of how you could really clean your solution up and make it more flexible by using some OOP:
enum Orientation {
Horizontal,
Vertical
};
struct Ship {
Ship(unsigned l = 1, bool o = Horizontal) : length(l), orientation(o) {}
unsigned char length;
bool orientation;
};
class Grid {
public:
Grid(const unsigned w = 8, const unsigned h = 8) : _w(w), _h(h) {
grid.resize(w * h);
foreach (Ship * sp, grid) {
sp = nullptr;
}
}
bool addShip(Ship * s, unsigned x, unsigned y) {
if ((x <= _w) && (y <= _h)) { // if in valid range
if (s->orientation == Horizontal) {
if ((x + s->length) <= _w) { // if not too big
int p = 0; //check if occupied
for (int c1 = 0; c1 < s->length; ++c1) if (grid[y * _w + x + p++]) return false;
p = 0; // occupy if not
for (int c1 = 0; c1 < s->length; ++c1) grid[y * _w + x + p++] = s;
return true;
} else return false;
} else {
if ((y + s->length) <= _h) {
int p = 0; // check
for (int c1 = 0; c1 < s->length; ++c1) {
if (grid[y * _w + x + p]) return false;
p += _w;
}
p = 0; // occupy
for (int c1 = 0; c1 < s->length; ++c1) {
grid[y * _w + x + p] = s;
p += _w;
}
return true;
} else return false;
}
} else return false;
}
void drawGrid() {
for (int y = 0; y < _h; ++y) {
for (int x = 0; x < _w; ++x) {
if (grid.at(y * w + x)) cout << "|S";
else cout << "|_";
}
cout << "|" << endl;
}
cout << endl;
}
void hitXY(unsigned x, unsigned y) {
if ((x <= _w) && (y <= _h)) {
if (grid[y * _w + x]) cout << "You sunk my battleship" << endl;
else cout << "Nothing..." << endl;
}
}
private:
QVector<Ship *> grid;
unsigned _w, _h;
};
The basic idea is create a grid of arbitrary size and give it the ability to "load" ships of arbitrary length at arbitrary coordinates. You need to check if the size is not too much and if the tiles aren't already occupied, that's pretty much it, the other thing is orientation - if horizontal then increment is +1, if vertical increment is + width.
This gives flexibility to use the methods to quickly populate the grid with random data:
int main() {
Grid g(20, 20);
g.drawGrid();
unsigned shipCount = 20;
while (shipCount) {
Ship * s = new Ship(qrand() % 8 + 2, qrand() %2);
if (g.addShip(s, qrand() % 20, qrand() % 20)) --shipCount;
else delete s;
}
cout << endl;
g.drawGrid();
for (int i = 0; i < 20; ++i) g.hitXY(qrand() % 20, qrand() % 20);
}
Naturally, you can extend it further, make hit ships sink and disappear from the grid, make it possible to move ships around and flip their orientation. You can even use diagonal orientation. A lot of flexibility and potential to harness by refining an OOP based solution.
Obviously, you will put some limits in production code, as currently you can create grids of 0x0 and ships of length 0. It's just a quick example anyway. I am using Qt and therefore Qt containers, but its just the same with std containers.
I tried to rewrite your program in Java, it works as required. Feel free to ask anything that is not clearly coded. I didn't rechecked it so it may have errors of its own. It can be further optimized and cleaned but as it is past midnight around here, I would rather not do that at the moment :)
public static void main(String[] args) {
Random generator = new Random();
int Grid[][] = new int[8][8];
for (int battleShips = 0; battleShips < 5; battleShips++) {
boolean isHorizontal = generator.nextInt(2) == 0 ? true : false;
boolean battleShipFilled = false;
while (!battleShipFilled) {
// Select a random row and column for trial
int row = generator.nextInt(8);
int column = generator.nextInt(8);
while (Grid[row][column] == 1) {
row = generator.nextInt(8);
column = generator.nextInt(8);
}
int lengthOfBattleship = 0;
if (battleShips == 0) // Smallest ship should be of length 2
lengthOfBattleship = (battleShips + 2);
else // Other 4 ships has the length of 2, 3, 4 & 5
lengthOfBattleship = battleShips + 1;
int numberOfCorrectLocation = 0;
for (int k = 0; k < lengthOfBattleship; k++) {
if (isHorizontal && row + k > 0 && row + k < 8) {
if (Grid[row + k][column] == 1)
break;
} else if (!isHorizontal && column + k > 0 && column + k < 8) {
if (Grid[row][column + k] == 1)
break;
} else {
break;
}
numberOfCorrectLocation++;
}
if (numberOfCorrectLocation == lengthOfBattleship) {
for (int k = 0; k < lengthOfBattleship; k++) {
if (isHorizontal)
Grid[row + k][column] = 1;
else
Grid[row][column + k] = 1;
}
battleShipFilled = true;
}
}
}
}
Some important points.
As #Kindread said in an another answer, the code has an infinite loop condition which must be eliminated.
This algorithm will use too much resources to find a solution, it should be optimized.
Code duplications should be avoided as it will result in more maintenance cost (which might not be a problem for this specific case), and possible bugs.
Hope this answer helps...