Jewels - Some Comments and a (personal) moan

Back to General discussions forum

CSFPython     2023-09-22 17:41:01

As one of the very few people who doesn't own a mobile phone (partly as a matter of principle but also because I would need a magnifying glass to use the small screen and keyboard) I have not yet seen the game of Jewels. Nevertheless, Rodion's description is very clear so there is no need for any prior knowledge. After reading the problem description I thought that it was quite likely that potential solutions would run into time problems. I decided to investigate this by writing a simulation of the game (as described by Rodion) with an algorithm to choose the next move.

I found that, with the 8 x 8 board, that the valid moves often ran out before 200 moves had been made. Reaching move 500 was fairly rare and I never saw a game reach move 1000. For the three larger boards, reaching move 1000 was quite common and happened about 80% of the time for the 13 x 13 board. The number of valid moves available to choose from was often as large as 40 to 50 with the bigger boards. A simple algorithm which just finds a valid move and submits this will have no difficulty in reaching 1000 moves (where possible) on all 4 boards within the given time limit. However, if we want to make a reasonable choice from among the available valid moves then the amount of processing time needed goes up dramatically. There is scope for sophistication in choosing the optimum move but this is then dependent on the random choice of jewels which appear in the fill process; so is probably not worth pursuing. I decided not to consider any sophistications but just to compare the valid moves available and to choose the one which scores the most. For my Python program, a game, using all 4 boards, had a processing time of around 12 seconds. Almost 5 seconds of this is for the 13 x 13 board. I am fairly certain that 12 seconds would be outside of the time limit for the problem. But it might be the case that Lua is significantly faster than Python so the time limit might not be exceeded.

It would be interesting to know how the system for checking solutions has been set up. I assume that submission of a move which does not get a score would lead to some sort of error message. Otherwise there seem to be three reasons for ending a game. Either 1000 moves have been played, or the Quit move has been used, or the program has run out of time. In all three of these cases there should be a score to add to the total. I would hope that running out of time would not nullify the score. I would also hope that running out of time would not prevent testing of the algorithm on the larger boards. Scoring is likely to be highest on the largest board.

If timing is likely to be a problem then there are several ways of dealing with it. Reducing the maximum number of moves below 1000 is a possibility but this penalises simpler algorithms which will not need so much time; so I suggest keeping the limit of 1000. If it is straightforward to do so, I would recommend a separate time limit for each board size. These time limits should be roughly in proportion to the area of the board. Then an algorithm which runs out of time on a board is credited with the score accumulated on that board, up to that time. The algorithm then starts afresh with the next larger board.

There is one rather obvious question at this point. Why did I not simply write my test programs in Lua? The answer (unfortunately) is that I have zero familiarity with the language, despite having submitted solutions to the previous Lua problems on this site. I suffer from the problem that my retention of new ideas (which was never good) has deteriorated significantly over recent years. Several days after writing a program in Lua I cannot remember anything about the language. This includes the basic syntax, the special characters, loop structures, the common reserved words, whether or not indentation is used, etc. It is very frustrating! I make a point of using Python several times each week to avoid the same problems occurring with Python. Even then I work with a fairly minimal sub-set of the Python language so that there is less to forget and to minimise the number of times that I have to look up something which I use fairly frequently. Jewels is a challenge problem. The challenge for me is not in writing the solution or even writing it in Lua. (I am sure that it is just as easy to write a solution in Lua as it is in Python). The challenge is in assimilating all of the language ideas needed to write the solution and then writing it before the ideas have gone. Doing this in one long period of time seems to work best. I shall begin by reading Rodion's Introduction to Lua, for the nth time (n-1 being the previous number of Lua problems on the site). I shall then use the reference manual and other online sources to answer questions like "How do you do X in Lua?" and "Is X even available in Lua?" knowing that I have probably already looked for these things several times before when doing previous problems.

Rodion (admin)     2023-09-22 19:53:40
User avatar

Dear Clive!

Thanks a lot for such a detailed analysis! I had some ideas on "heuristical" improvement of choosing the moves, and now there is quite a heap of thoughts after reading your thoughts :)

But let's put them aside for a moment and address the second part! First of all thanks for sharing this opinion, as, since adding these few problems in Lua, I had no clear idea of how good or bad it is for Python programmer. As I mentioned sometimes, I feel myself poor in Python - some may think I'm joking, but that's the fact - I remember general syntax but I always google up various library calls and generally my Python programs won't start until fixing a handful of mistakes. Same with Lua for me. Same even with languages I use at work. Same with PHP (the only serious thing I wrote in it - is this site and many "checkers" for problems).

So to say, I feel similar difficulties with all languages and thus have difficulty in judging how it works for other people. Hence your "moan" or feedback is quite "constructive".

Now, situation is like this - during the course of "integrating" Lua for the site I found that it should be possible to call Python in similar manner (they are both executed at the same "sandbox" server which runs solutions when the corresponding button in the user interface is pressed).

There was some special point in using Lua for certain problems - to be able to execute them "in browser", but it doesn't make sense for this problem. I just completely missed this point somehow. Actually I don't think there are many problems utilizing this feature yet. Perhaps only Mandelbrot?

So perhaps let me try to scratch up my Python and make this problem bi-lingual if possible (I'd prefer to keep Lua version as I suspect it truly may give better performance in exchange for clumsier coding).

There perhaps may be some issues I don't understand now (something about security/hacking the checker code) but I guess it's hard to know beforehand, let's try to try!

I also definitely missed that sample code for playing the game itself could (should) be published so that people don't need to write it from scratch... Or perhaps this should remain as a part of challenge?

CSFPython     2023-09-22 19:59:50

Rodion, There is absolutely no need to make this problem available for Python. I think it is really good that you put in problems to encourage the use of other languages like Lua. I described my personal circumstances in case people might be interested. There was no intention to suggest that I did not wish to see problems appearing in Lua. I have already started the process of working towards the Lua program and hope to finish before the end of tomorrow.

Rodion (admin)     2023-09-23 07:26:56
User avatar

Ha-ha, Clive, thanks, but it's too late! I get used to idea that your hints (even in form of personal impression) are bit too important to ignore! :) And anyway it was something good to try.

So (as we say "the devil is not as horrible as it is depicted") I just have managed to complete Python version of the executing script and added it to the problem, slightly updating description.

Some concern is that I could mistake somewhere while rewriting (especially with sad 1-based lua indices), but my simple test solution now yields same score in both languages. Tell at once if some inconsistency is suspected!

There is some drawback yet that server uses Python 3 while you prefer to keep to the 2-nd version. Bit further complication is that it is not the top-latest version (but as it is PyPy it should be faster than common Python).

Python gurus are also invited to try whether it is possible to cheat somehow (e.g. finding and modifying some executor variables).

gardengnome     2023-09-23 07:40:04
User avatar

Let's see what can be done here ...

Rodion (admin)     2023-09-23 09:08:27
User avatar

Hm-m-m, here was a joke about updating global overall_score variable... but is no more :o You probably decided not to spoil my secret :) Hopefully executing script leaks few or none of global variables... and board is cloned when passed into the user's function... but I don't know if it is enough. Just today learnt of the nonlocal keyword. Python definitely is extremely complicated below the surface :)

Back in November 2012 I tried to create small challenge of javascript bots playing dice game (the one you've kindly shared materials about to me) - there was a small prize, I guess $100 proposed - and two smart fellows figured out the way to predict built-in random generator. So it is hard to tell what way of hackerism could be expected...

Rodion (admin)     2023-09-23 10:52:00
User avatar

Noticed failing submissions - seemingly Mathias' code sometimes return None from the function and it fails to unpack. Added check of the returned value, should be explicit message now. Thanks.

CSFPython     2023-09-23 10:55:59

Rodion, Python 3 is not a problem for me. My code doesn't use any input or output or any divisions, so should work without any changes. The disappointing part is that it runs four times slower in Pypy2 (or Pypy3) compared with normal Python. I have noticed that Pypy is often slower than Python for some kinds of program. For others it can be very much faster. I tried submitting the program but it is timed out. I assume that you have decided not to go with my suggestions for dealing with the timing issue. In any case, given the speed problems with Pypy, I should probably aim to complete the Lua code and try that.

Rodion (admin)     2023-09-23 11:00:39
User avatar

The disappointing part is that it runs four times slower in Pypy2 (or Pypy3) compared with normal Python

It is very interesting observation! I shall try adding "normal python" also, it just may take some time... But perhaps it is the difference of execution power between your machine and server?

Is there no way to slightly limit your search-branching? Your code anyway gives good score... I just suspect that it may be equally timeouting in Lua (quick test indicates that luajit is perhaps 2 times faster than pypy, so I don't expect extreme improvement)...

gardengnome     2023-09-23 11:17:21
User avatar

Hi Rodion,

I've tried to just pass this problem but my solution always times out. I've stripped the solution quite a lot back - no copies of any lists / arrays are created, scoring is only approximate and the cascading stages are completely ignored - yet it is still too slow. I am not sure whether I am missing something or whether the restrictions are simply too much.

Also, if PyPy is used, are the different tests run as "one program" or as several? PyPy always needs some initial processing time (as it compiles the code).

Rodion (admin)     2023-09-23 11:29:26
User avatar

Hi Mathias!

I tried your code locally (with the same executor which is used by problem). It works 0.6 sec on my machine. My own "trivial" solution runs in 0.08 though it ends in about 200+ moves... I think perhaps the would be to reduce this max number of moves (1000)? It was not meant that any run should try getting to this limit, so it is not some carefully tested limit, rather simply technical one...

gardengnome     2023-09-23 11:31:37
User avatar

Many thanks. I'll try to strip it back further a bit later.

Rodion (admin)     2023-09-23 11:32:53
User avatar

(reduced max moves to 300 for now - if we would like we can enlarge it back later... actually one can count moves or check running time inside the program of course...)

CSFPython     2023-09-23 11:34:49

Rodion, There is no need to add normal Python. I don't thonk the speed issue is anything to do with the differences between my machine and the server. The difference seems to be entirely due to the type of processing being done. I have often used Pypy for the speed advantage; which has sometimes been as much as 40 times better. Equally I have often been very disappointed when Pypy has run considerably slower than Python.

For many of your problems there is an end point to be reached within a time limit. For this problem each move is an end point in itself. If you are able to stop a game after 1000 moves and count the score up to that point, are you also able to stop a game after X seconds and count the score up to that point? If this is easy to do then it makes sense to have separate time limits on each of the 4 boards (as I explained in my earlier post).

As I tried to explain in the earlier post, as soon as you change the algorithm from finding a valid move to finding the best of the valid moves, you immediately hit a huge increase in processing requirements. The only sensible way to choose between moves is to choose the move which scores the most. If you have 45 moves to choose from and have to find the score for each of those .... Well you clearly know what is involved.

I will press ahead with the Lua. I don't know what your overall time limit is but if Lua is 10 times faster than Python that could be enough.

If there is no easy way to have 4 time limits with scores up to that point, and the Lua code is not fast enough; I always have the fallback position of submitting an algorithm which submits the first move to be found. However, this doesn't seem to be in the spirit of the problem. I am interested to know your thoughts on my proposal for 4 separate time limits (proportional to board area) and all scores up to each time limit being counted.

Rodion (admin)     2023-09-23 12:08:26
User avatar

you immediately hit a huge increase in processing requirements.

that's true... I poorly investigated the problem myself yet, but the idea is if we hypothetically allow unlimited time, it won't be as much a problem. with limit however it may require guessing about good moves rather than finding them, similarly how the first chess programs worked... just a suggestion though :)

CSFPython     2023-09-23 12:53:00

Rodion, I am not suggesting that we have unlimited time. Say, for example, that the 12 x 12 board had a time limit of 0.5 seconds. A simple algorithm could easily get through all 1000 moves in that time but would then be halted by your checker. The scores for all 1000 moves would then be added to the running total. A more demanding algorithm would not be able to get through 1000 moves in this time limit. Suppose that the time limit kicked in after 96 moves had been made. No more moves will be allowed but the score for these 96 moves should be counted. Both algorithms should be able to perform on all 4 boards. In each case the stopping criterion will be the first to occur of 1000 moves, Quit or timeout. I fail to see why the timeout criterion should wipe out the score that has been accumulated up to that point. So we are treating all algorithms equally. More demanding algorithms will be stopped after fewer moves have been made; but they should get an equal chance on all four boards.

If you cannot implement a time cutoff in this way please say so and I will stop making these suggestions.

Unlike chess, a guessing strategy in this case is little better than choosing the first valid move. Certainly you would choose a line of 4 over a line of 3 but the real score changes occur when you look at the cascades.

CSFPython     2023-09-23 14:38:31

Rodion, I finally completed the Lua program only to find that this is also timed out. Sadly, I have now disabled the part of the Lua program which chooses the best move. The program will still choose a line of 4 over a line of 3 but very little more than that. It is better than a random choice of move but not by much.

This is now accepted by the system!

Rodion (admin)     2023-09-23 18:16:32
User avatar

I fail to see why the timeout criterion should wipe

Ah, that is regretfully very simple - there is no reliable way to calculate time from the executing script itself (i.e. from the program which calls user's suggest function) - so time limit is instead imposed "externally" - there is some system command which calls runs the whole program but kills it after certain time elapses. I can't wrap my mind about how to return score in this case. Actually it is not even directly related to this problem - just general feature of our "sandbox".

Though perhaps some workaround could be made like this - executing script should check time internally, e.g. before every call to suggest - and if it exceeds the limit, stop calling suggest further, but return with currently achieved score, as you say. Meanwhile "hard" time limit should be set to some value slightly larger than this "soft" limit.

This is not 100% reliable (in case single call to suggest function suddenly takes too much time), but is close to what you propose (unless I'm mistaken?) - so I'll try to see how to do this.

Other things I think may be like increasing general time limit at all. I don't think "sandbox" server is heavily used so this shouldn't lead to much concurrency...

However!

I still think that the problem is not necessarily about "choosing the best move" properly. Or rather one can't exactly know what move is the best due to randomness.

The program will still choose a line of 4 over a line of 3

as you mentioned above, even this is not provably best strategy for naive algorithm :) perhaps one should remove smaller lines in hope that lines of 4 will later get involved in cascade...

The good point about such "open problems" or challenges is that, as I also can participate. I just sent my naive algorithm - it yielded something over 20k. I tried some idea I had - about "guessing" the move rather than properly searching. To my own surprise it improved tenfold...

About Lua speed

Sorry for this feature may have been missed, as was added just few days ago - there is magic of putting comment

--luajit

in the first line (anything with "luajit") - which makes it execute with alternative implementation of lua, which is perhaps 10 times faster (similarly to how pypy relates to normal python). small issue is this "luajit" conforms to lua 5.1, so there may be minor deviations (I found table.unpack function is global instead and bitshift operations do not exist). This info was added to lua intro page also, but mainly thats just it.

P.S. my current solution fits time limit whitout this optimized version of lua though. simply because it is naive.

Rodion (admin)     2023-09-23 18:24:39
User avatar

P.P.S. I have some concern though about testing the solution on boards of these four sizes. In small-score solutions it looks like small tweaks may result in significant changes of the total score. Perhaps running, say, on 5-7 boards of the same size (say 10 or 11) perhaps just for 150-200 moves will lead to better "dispersion"? The reason why there are more than one board is that we remember how task about "life in asm" was solved (unless I'm mistaken) by "fitting" solution to the test data. I hope that summing the score over 2 or more boards should confuse similar approaches, but I may be mistaken again...

Rodion (admin)     2023-09-23 18:30:26
User avatar

P.P.P.S. the details of your advanced approach are bit difficult to grasp for me, but I noticed deepcopy usage and this probably is easy but not efficient way to restore the board after changes, especially if changes were not very large. I mean, if we are to work this way with tracing various moves and cascades, we probably may need to improve the manipulations with the board itself.

Rodion (admin)     2023-09-23 18:58:56
User avatar

P.P.P.P.S. Sorry for that many "ppppps", but I just thought that time limit you propose you can meanwhile implement easily yourself.

In lua it is just like calling os.clock() at start of the suggest function and return immediately (e.g. with 1, 1, Q if the value is close to 1 second (or perhaps 0.9 to be sure). As I understand the function simply returns time elapsed from start.

In python it is just a bit more difficult, I guess like this:

  • check if t0 global variable is not set, and if so, set it to time.time()
  • check if time.time() - t0 < 0.9

(sorry I again don't remember proper syntax for checking the global variable)

CSFPython     2023-09-23 19:19:56

Rodion, Thanks for the helpful explanations. One possible way to get around the difficulty of keeping the score before a timeout is to give the player some control over when to stop. If the function suggest is passed the move number for the current board, along with the board itself then the algorithm can call the Quit command if it decides that more moves are likely to result in a time out. For example, if I suspected that making more than 80 moves on a particular board would be likely to put my program over the time limit then I can simply add a command to quit on the 80th move.

I understand your points about move choosing but I think these tend to get swamped by the cascades. At least you can calculate the score for every valid move and then choose on that basis; but this then gets into time problems. Nevertheless I noticed that you did get a reasonable score with your algorithm. In my experiments with the problem on my own computer, I sometimes had scores with 9 digits; almost a billion. This did require a total time of 12 seconds in Python. However, the scores did vary considerably from game to game. Randomness and the exponential rise in score with each extra jewel are the reason for the huge fluctuations.

Thanks for the tip about --luajit. I tried adding this. Even without any other changes to the program it produced an error message that the program was broken. Removing --luajit got the routine working again. I assume that this is due to the 5.1 version, as you suggest. At this stage I don't feel like re-writing the code in a different version of Lua. I am happy to stick with what I have.

I don't think you need worry about anyone managing to gain sufficient information to determine what the puzzle is. There are far too many variables for this. However, you have commented yourself on the big changes in score from small tweaks. That is due to the nature of this game. Your decision to use several boards goes some way towards evening this out but the substantial variability will always be there.

I agree with your comments on the use of deepcopy. I did not use this to reverse the change made by swapping two jewels. I did decide to use it for reversing a cascade sequence. Some of the cascades are quite complex and several may occur one after the other. So deepcopy was used only once (in each case) to reverse a possible sequence of cascades.

gardengnome     2023-09-23 19:30:29
User avatar

Just that this doesn't happen to anybody else - I completely missed the following until just now:

In case when you want to stop (e.g. not seeing any good move) return Q as direction - e.g. 1, 1, Q.

Rodion (admin)     2023-09-23 19:30:56
User avatar

UPD: about luajit thanks for quick hint - it just occurred to me some incompatibilities could be amended in executing script (e.g. defining undefined function). Feel free to retry - probably it may work better now (can't say for timeout).

As about passing the number of the move - good point, thanks - shall try adding this just after walking the dog right now :)

I completely missed the following until just now:

Oh, sorry for that. Description is a bit clumsy and perhaps it was better to stop the game without breaking on returning something like None - but on the other hand I thought user may want to know something is not going right... not sure.

Rodion (admin)     2023-09-23 19:34:16
User avatar

Hey... How you did this? Million points? :o (rhethorical question)

gardengnome     2023-09-23 19:34:54
User avatar

The statement is very clear, I just completely missed it.

Score: no idea, and no I didn't add 34 millions to the global overall score variable. :) Maybe I got lucky as 2^25 ~ 34M.

CSFPython     2023-09-23 19:45:08

Rodion, I have just noticed your latest post. I assume from your comments that the total time limit for the problem is 1 second. This is the first time that I have realised this. It might be helpful to give that information in the problem description, for problems of this nature. I am intrigued by the use of os.clock(). Does this measure time from the point where the server starts the puzzle? Is it also the case that the time simply continues to increase as each of the boards are worked in turn? If this is the case I could set time limits of say 0.1 s, 0.3 s, 0.6s and 0.95 s for each of the 4 boards. That assumes that they are presented in the order 8, 11, 12, 13. That information would also need to be made available to allow strategies like this. In Python I had not realised that global variables would be preserved between moves, still less between different boards. This gives me another possibility to try. However, I will have a break for a day or two first.

And to Mathias, congratulations on the score. I'm impressed that your optimisation skills allowed you to get it in, within the time limit.

Rodion (admin)     2023-09-24 05:52:53
User avatar

Sorry for the delay - now the suggest function can take additional parameters:

def suggest(board, boardNumber, moveNumber, timeElapsed)  #boardNumber and moveNumber are 1-based

Meanwhile compatibility with old single-argument version is preserved. Problem description is updated on this.

I sometimes had scores with 9 digits; almost a billion

yep, I now start to realize that probably the scoring function with simple doubling on every additional jewel removed perhaps was not the greatest choice :(

that the total time limit for the problem is 1 second

yes, sorry for that - it was added to the description at some early stage, but not at beginning... perhaps it was better to be added to error message itself.

If this is the case I could set time limits of say 0.1 s, 0.3 s, 0.6s and 0.95

yes, it is the case - all four "runs" are executed sequentially by the same script so time simply increases. Perhaps one may decide to return minimal score for all smaller boards and invest all time on the larger one.

CSFPython     2023-09-24 19:21:31

Rodion, Thanks for all the effort with this problem. The additional parameters are very helpful, especially the move number, which makes it easy to quit before the time runs out. I notice that you have left the maximum number of moves at 300. You may as well leave it at this value now. It makes it less likely that people will run into time problems.

Rodion (admin)     2023-09-25 17:24:08
User avatar

Clive, thanks a lot to you and Mathias, it seems with combined efforts we managed to greatly improve this problem :)

Meantime I see that the task in this condition poorly suits my intention of announcing it a "prize challenge", partly due to extreme growth of score. Perhaps we'll had some new game-like problem using the lessons learnt this on this occasion. I remember someone suggested "Tetris" (though honestly I poorly understand what should be algorithm to automatically play it). Anyway, suggestions are welcome!

P.S. there was discrepancy in normalized score in the table... I fixed it but now think we probably do not need this column at all now, when challenges do not affect "enlightenment" points.

Please login and solve 5 problems to be able to post at forum