def reset_puzzle_pieces(puzzle): """ Puzzles that are reset will reuse the same pieces as before and are not rerendered. """ # TODO: Reset the redis puzzle token so players will be required to refresh # the browser if they had the puzzle open. cur = db.cursor() result = cur.execute( fetch_query_string("select_all_from_puzzle_for_id.sql"), { "id": puzzle }, ).fetchall() if not result: cur.close() raise DataError("No puzzle found with that id.") (result, col_names) = rowify(result, cur.description) puzzle_data = result[0] # Update puzzle status to MAINTENANCE r = requests.patch( "http://{HOSTAPI}:{PORTAPI}/internal/puzzle/{puzzle_id}/details/". format( HOSTAPI=current_app.config["HOSTAPI"], PORTAPI=current_app.config["PORTAPI"], puzzle_id=puzzle_data["puzzle_id"], ), json={"status": MAINTENANCE}, ) if r.status_code != 200: raise Exception("Puzzle details api error") sse.publish( "status:{}".format(MAINTENANCE), channel="puzzle:{puzzle_id}".format( puzzle_id=puzzle_data["puzzle_id"]), ) # Transfer any redis piece data out first. transfer(puzzle, cleanup=True) # timeline ui should only show when the puzzle is in 'complete' status. archive_and_clear(puzzle) (x1, y1, x2, y2) = (0, 0, puzzle_data["table_width"], puzzle_data["table_height"]) (result, col_names) = rowify( cur.execute(query_select_top_left_piece, { "puzzle": puzzle_data["id"] }).fetchall(), cur.description, ) cur.close() topLeftPiece = result[0] allPiecesExceptTopLeft = list(range(0, puzzle_data["pieces"])) allPiecesExceptTopLeft.remove(topLeftPiece["id"]) # Randomize piece x, y. Reset the parent new_piece_properties = [] for piece in allPiecesExceptTopLeft: x = randint(x1, x2) y = randint(y1, y2) new_piece_properties.append({ "x": x, "y": y, "parent": None, "status": None, "id": piece }) current_app.logger.debug(new_piece_properties) r = requests.patch( "http://{HOSTAPI}:{PORTAPI}/internal/puzzle/{puzzle_id}/pieces/". format( HOSTAPI=current_app.config["HOSTAPI"], PORTAPI=current_app.config["PORTAPI"], puzzle_id=puzzle_data["puzzle_id"], ), json={"piece_properties": new_piece_properties}, ) if r.status_code != 200: raise Exception( "Puzzle pieces api error. Failed to patch pieces. {}".format( r.json()))
def reset_puzzle_pieces(puzzle): """ Puzzles that are reset will reuse the same pieces as before and are not rerendered. """ # TODO: Reset the redis puzzle token so players will be required to refresh # the browser if they had the puzzle open. cur = db.cursor() result = cur.execute( fetch_query_string("select_all_from_puzzle_for_id.sql"), { "id": puzzle }, ).fetchall() if not result: cur.close() raise DataError("No puzzle found with that id.") (result, col_names) = rowify(result, cur.description) puzzle_data = result[0] # Update puzzle status to MAINTENANCE r = requests.patch( "http://{HOSTAPI}:{PORTAPI}/internal/puzzle/{puzzle_id}/details/". format( HOSTAPI=current_app.config["HOSTAPI"], PORTAPI=current_app.config["PORTAPI"], puzzle_id=puzzle_data["puzzle_id"], ), json={"status": MAINTENANCE}, ) if r.status_code != 200: raise Exception("Puzzle details api error") sse.publish( "status:{}".format(MAINTENANCE), channel="puzzle:{puzzle_id}".format( puzzle_id=puzzle_data["puzzle_id"]), ) # Transfer any redis piece data out first. transfer(puzzle, cleanup=True) # timeline ui should only show when the puzzle is in 'complete' status. archive_and_clear(puzzle) (table_width, table_height) = (puzzle_data["table_width"], puzzle_data["table_height"]) width = round(table_width / 2.5) height = round(table_height / 2.5) outline_offset_x = int((table_width - width) * 0.5) outline_offset_y = int((table_height - height) * 0.5) (result, col_names) = rowify( cur.execute(query_select_top_left_piece, { "puzzle": puzzle_data["id"] }).fetchall(), cur.description, ) cur.close() topLeftPiece = result[0] allPiecesExceptTopLeft = list(range(0, puzzle_data["pieces"])) allPiecesExceptTopLeft.remove(topLeftPiece["id"]) # Randomize piece x, y. Reset the parent # Uses same method of random piece placement that piecemaker uses. r = requests.get( "http://{HOSTAPI}:{PORTAPI}/internal/puzzle/{puzzle_id}/pieces/". format( HOSTAPI=current_app.config["HOSTAPI"], PORTAPI=current_app.config["PORTAPI"], puzzle_id=puzzle_data["puzzle_id"], )) if r.status_code != 200: raise Exception( "Puzzle pieces api error. Failed to get pieces. {}".format( r.json())) piece_properties = r.json()["immutable_piece_properties"] # Simulate the pieces.json file from piecemaker piece_bboxes = {} for pc_id in allPiecesExceptTopLeft: # Just use 0 and 0 for the x, and y here since PM doesn't store that. piece_bboxes[pc_id] = [ 0, 0, piece_properties[str(pc_id)]["w"], piece_properties[str(pc_id)]["h"], ] pieces_distribution = random_outside( table_bbox=[0, 0, table_width, table_height], outline_bbox=[ outline_offset_x, outline_offset_y, outline_offset_x + width, outline_offset_y + height ], piece_bboxes=piece_bboxes, regions=("left_side", "top_middle", "bottom_middle")) new_piece_properties = [] for piece in allPiecesExceptTopLeft: x = pieces_distribution[piece][0] y = pieces_distribution[piece][1] new_piece_properties.append({ "x": x, "y": y, "parent": None, "status": None, "id": piece }) r = requests.patch( "http://{HOSTAPI}:{PORTAPI}/internal/puzzle/{puzzle_id}/pieces/". format( HOSTAPI=current_app.config["HOSTAPI"], PORTAPI=current_app.config["PORTAPI"], puzzle_id=puzzle_data["puzzle_id"], ), json={"piece_properties": new_piece_properties}, ) if r.status_code != 200: # Update puzzle status to BUGGY_UNLISTED r = requests.patch( "http://{HOSTAPI}:{PORTAPI}/internal/puzzle/{puzzle_id}/details/". format( HOSTAPI=current_app.config["HOSTAPI"], PORTAPI=current_app.config["PORTAPI"], puzzle_id=puzzle_data["puzzle_id"], ), json={"status": BUGGY_UNLISTED}, ) if r.status_code != 200: raise Exception("Puzzle details api error") sse.publish( "status:{}".format(BUGGY_UNLISTED), channel="puzzle:{puzzle_id}".format( puzzle_id=puzzle_data["puzzle_id"]), ) raise Exception( "Puzzle pieces api error. Failed to patch pieces. {}".format( r.json()))
def do_task(self): super().do_task() made_change = False cur = db.cursor() for (low, high) in SKILL_LEVEL_RANGES: in_queue_puzzle_count = cur.execute( read_query_file("get_in_queue_puzzle_count.sql"), { "low": low, "high": high }, ).fetchone()[0] if in_queue_puzzle_count <= self.minimum_count: (result, col_names) = rowify( cur.execute( read_query_file("select_random_puzzle_to_rebuild.sql"), { "status": COMPLETED, "low": low, "high": high }, ).fetchall(), cur.description, ) if result: for completed_puzzle in result: puzzle = completed_puzzle["id"] current_app.logger.debug( "found puzzle {id}".format(**completed_puzzle)) # Update puzzle status to be REBUILD and change the piece count pieces = random.randint( max( int(current_app.config["MINIMUM_PIECE_COUNT"]), max(completed_puzzle["pieces"] - 400, low), ), min(high - 1, completed_puzzle["pieces"] + 400), ) r = requests.patch( "http://{HOSTAPI}:{PORTAPI}/internal/puzzle/{puzzle_id}/details/" .format( HOSTAPI=current_app.config["HOSTAPI"], PORTAPI=current_app.config["PORTAPI"], puzzle_id=completed_puzzle["puzzle_id"], ), json={ "status": REBUILD, "pieces": pieces, "queue": QUEUE_END_OF_LINE, }, ) if r.status_code != 200: current_app.logger.warning( "Puzzle details api error. Could not set puzzle status to rebuild. Skipping {puzzle_id}" .format(**completed_puzzle)) continue completed_puzzle["status"] = REBUILD completed_puzzle["pieces"] = pieces # Delete any piece data from redis since it is no longer needed. query_select_all_pieces_for_puzzle = ( """select * from Piece where (puzzle = :puzzle)""") (all_pieces, col_names) = rowify( cur.execute(query_select_all_pieces_for_puzzle, { "puzzle": puzzle }).fetchall(), cur.description, ) deletePieceDataFromRedis(redis_connection, puzzle, all_pieces) job = self.queue.enqueue_call( func="api.jobs.pieceRenderer.render", args=([completed_puzzle]), result_ttl=0, timeout="24h", ) archive_and_clear(puzzle) made_change = True cur.close() if made_change: self.log_task()
def do_task(self): super().do_task() made_change = False cur = db.cursor() in_queue_puzzles_in_piece_groups = current_app.config[ "MINIMUM_IN_QUEUE_PUZZLES_IN_PIECE_GROUPS"].copy() in_queue_puzzles_in_piece_groups.reverse() for (low, high, minimum_count) in map( lambda x: (x[0], x[1], in_queue_puzzles_in_piece_groups.pop()), current_app.config["SKILL_LEVEL_RANGES"], ): if minimum_count == 0: continue in_queue_puzzle_count = cur.execute( read_query_file("get_in_queue_puzzle_count.sql"), { "low": low, "high": high }, ).fetchone()[0] if in_queue_puzzle_count <= minimum_count: (result, col_names) = rowify( cur.execute( read_query_file("select_random_puzzle_to_rebuild.sql"), { "status": COMPLETED, "low": max(0, low - 500), "high": high + 500, }, ).fetchall(), cur.description, ) if not result: # try again with wider range of puzzle piece counts (result, col_names) = rowify( cur.execute( read_query_file( "select_random_puzzle_to_rebuild.sql"), { "status": COMPLETED, "low": max(0, low - 2000), "high": high + 2000, }, ).fetchall(), cur.description, ) if result: for completed_puzzle in result: puzzle = completed_puzzle["id"] current_app.logger.debug( "found puzzle {id}".format(**completed_puzzle)) # Update puzzle status to be REBUILD and change the piece count low_range = max( int(current_app.config["MINIMUM_PIECE_COUNT"]), min( max(low, (high - 400)), max(low, (completed_puzzle["pieces"] - 400)), ), ) high_range = min( high, max((low + 400), (completed_puzzle["pieces"] + 400))) pieces = random.randint(low_range, high_range) sleep(API_REQUESTS_LIMIT_RATE) r = requests.patch( "http://{HOSTAPI}:{PORTAPI}/internal/puzzle/{puzzle_id}/details/" .format( HOSTAPI=current_app.config["HOSTAPI"], PORTAPI=current_app.config["PORTAPI"], puzzle_id=completed_puzzle["puzzle_id"], ), json={ "status": REBUILD, "pieces": pieces, "queue": QUEUE_END_OF_LINE, }, ) if r.status_code != 200: current_app.logger.warning( "Puzzle details api error. Could not set puzzle status to rebuild. Skipping {puzzle_id}" .format(**completed_puzzle)) continue completed_puzzle["status"] = REBUILD completed_puzzle["pieces"] = pieces # Delete any piece data from redis since it is no longer needed. query_select_all_pieces_for_puzzle = ( """select * from Piece where (puzzle = :puzzle)""") (all_pieces, col_names) = rowify( cur.execute(query_select_all_pieces_for_puzzle, { "puzzle": puzzle }).fetchall(), cur.description, ) deletePieceDataFromRedis(redis_connection, puzzle, all_pieces) job = self.queue.enqueue( "api.jobs.pieceRenderer.render", [completed_puzzle], result_ttl=0, job_timeout="24h", ) archive_and_clear(puzzle) made_change = True cur.close() if made_change: self.log_task()