indexing
description: "[
Highscore class. Highscore is stored locally. You can add and update entries,
sort and crop the highscore.
]"
date: "$Date$"
revision: "$Revision$"
class EM_LOCAL_HIGHSCORE
inherit
XM_CALLBACKS_FILTER_FACTORY
export {NONE} all end
EM_TIME_SINGLETON
export {NONE} all end
EM_SHARED_USER_DIRECTORY
export {NONE} all end
EM_SHARED_APPLICATION_ID
create
make, make_extended
feature -- Initialization
make is
-- create highscore
require
application_id_set: application_id.item /= void
do
make_extended (<<>>)
end
make_extended (the_other_keys: ARRAY [STRING]) is
-- create highscore with more keys
require
no_keywords: not has_keywords(the_other_keys)
the_other_keys /= void
application_id_set: application_id.item /= void
do
set_local_file (user_directory.item, application_id.item + "_highscore.xml")
create other_keys.make_from_array (the_other_keys)
other_keys.compare_objects
num_of_local_entries := 10
create local_highscore.make
create checksum_generator
create highscore_checksum_filter.make_null
end
feature -- Element change
set_num_of_local_entries (a_num_of_local_entries: like num_of_local_entries) is
-- Set `num_of_local_entries' to `a_num_of_local_entries'.
do
num_of_local_entries := a_num_of_local_entries
ensure
num_of_local_entries_assigned: num_of_local_entries = a_num_of_local_entries
end
set_local_file (a_directory: STRING; a_filename: STRING) is
-- set path for highscore file
require
a_filename /= void
a_directory.item (a_directory.count) = '/'
do
create highscore_dir.make (a_directory)
create highscore_file.make (a_directory + a_filename)
end
feature -- Read/Write
read_local is
-- read highscore from a local file
require
file_set: highscore_file /= void
do
parse_highscore_file (highscore_file)
end
write_local is
-- write local highscore to disc
require
highscore_file_set: highscore_file /= void
do
checksum_generator.reset
if not highscore_file.exists then
-- todo: i want to create the file, not open it yet
if not highscore_dir.exists then
highscore_dir.recursive_create_directory
end
if highscore_file.is_creatable then
highscore_file.create_read_write
highscore_file.close
end
end
if highscore_file.exists and then highscore_file.is_writable then
highscore_file.open_write
-- todo
-- Write out all elements in highscore table
highscore_file.put_string ("%N%N")
highscore_file.put_string ("<" + xml_section + ">%N")
from
local_highscore.start
until
local_highscore.after or local_highscore.index > num_of_local_entries
loop
highscore_file.put_string ("%T%N")
local_highscore.forth
end
checksum_generator.append_string (client_id.out)
highscore_file.put_string ("%T%N")
checksum_generator.generate_checksum
highscore_file.put_string ("%T%N")
highscore_file.put_string ("" + xml_section + ">%N")
highscore_file.close
else
debug
io.put_string ("Writing highcore file failed%N")
end
end
end
feature {NONE} -- Read/Write Implementation
parse_highscore_file (a_file: like highscore_file) is
-- read highscore entries from a local file if they are valid
require
file_set: a_file /= void
do
create highscore_parser.make
create highscore_xm_filter.make_null
highscore_parser.set_callbacks (callbacks_pipe (<>))
if a_file /= void and then a_file.exists and then a_file.is_readable then
a_file.open_read
a_file.read_stream (a_file.count)
highscore_parser.parse_from_string (a_file.last_string)
a_file.close
end
-- if file was parsed and the checksum is correct, integrate it into local
if highscore_xm_filter.highscore /= void and then highscore_checksum_filter.checksum = highscore_xm_filter.checksum then
client_id := highscore_xm_filter.client_id
create local_highscore.make_default
from
highscore_xm_filter.highscore.start
until
highscore_xm_filter.highscore.after
loop
add_entry_from_table (highscore_xm_filter.highscore.item_for_iteration)
highscore_xm_filter.highscore.forth
end
else
debug
io.put_string ("Could not read file.%N")
io.put_string ("That most probably means the file either didn't exist or the checksum is incorrect%N")
end
end
if client_id = 0 then
client_id := generate_client_id
end
sort_local_by_score (false)
highscore_parser := void
highscore_xm_filter := void
end
feature -- Add/Update entries
add_or_update_entry (a_name: STRING; a_score: INTEGER) is
-- add or update entry depending on if it's already present or not
require
name_valid: is_valid_string (a_name)
do
add_or_update_entry_extended (a_name, a_score, <<>>)
end
add_or_update_entry_extended (a_name: STRING; a_score: INTEGER; other_values: ARRAY[STRING]) is
-- add or update extended entry depending of it's already present or not
require
name_valid: is_valid_string (a_name)
right_number_of_values: other_values.count = other_keys.count
do
if has_entry (a_name) and is_better (a_name, a_score) then
update_entry_extended (a_name, a_score, other_values)
else
add_entry_extended (a_name, a_score, other_values)
end
end
add_entry (a_name: STRING; a_score: INTEGER) is
-- add entry to local highscore
require
name_valid: is_valid_string (a_name)
do
add_entry_extended (a_name, a_score, <<>>)
end
add_entry_extended (a_name: STRING; a_score: INTEGER; other_values: ARRAY[STRING]) is
-- add extended entry to local highscore
require
name_valid: is_valid_string (a_name)
right_number_of_values: other_values.count = other_keys.count
local
i: INTEGER
new_entry: EM_HIGHSCORE_ENTRY
do
-- todo: here I allocate for 'keys.count', but get keys.count + 1 entries in the table?
create new_entry.make (other_keys.count)
new_entry.set_name (a_name)
new_entry.set_score (a_score)
from
i := 1
until
i > other_values.count
loop
new_entry.put (other_values.item (i), other_keys.item(i))
i := i + 1
end
local_highscore.put_last (new_entry)
end
update_entry (the_name: STRING; the_score: INTEGER) is
-- update entry in local highscore
require
name_valid: is_valid_string (the_name)
do
update_entry_extended (the_name, the_score, <<>>)
end
update_entry_extended (the_name: STRING; a_score: INTEGER; other_values: ARRAY[STRING]) is
-- update extended in local highscore
require
name_valid: is_valid_string (the_name)
right_number_of_values: other_values.count = other_keys.count
local
updated: BOOLEAN
the_entry: like highscore_entries
i: INTEGER
do
-- then update this value in the table
from
local_highscore.start
until
local_highscore.after or updated
loop
if local_highscore.item_for_iteration.name.is_equal (the_name) then
the_entry := local_highscore.item_for_iteration
the_entry.set_name (the_name)
the_entry.set_score (a_score)
from
i := 1
until
i > other_keys.count
loop
the_entry.replace (other_values.item (i), other_keys.item (i))
i := i + 1
end
updated := true
end
local_highscore.forth
end
end
has_entry (a_name: STRING): BOOLEAN is
-- check if name is in local highscore
require
name_not_void: a_name /= Void
do
from
local_highscore.start
until
local_highscore.after or Result = true
loop
-- todo
Result := local_highscore.item_for_iteration.name.is_equal (a_name)
local_highscore.forth
end
end
is_better (a_name: STRING; a_score: INTEGER): BOOLEAN is
-- check if 'a_score' of 'a_name' is better than the one already in highscore
require
has_entry (a_name)
do
from
local_highscore.start
until
local_highscore.after or Result = true
loop
if local_highscore.item_for_iteration.name.is_equal (a_name) then
Result := a_score > local_highscore.item_for_iteration.score
end
local_highscore.forth
end
end
is_valid_string (a_string: STRING): BOOLEAN is
-- check if string is valid (doesn't contain special characters
local
i: INTEGER
do
Result := (a_string /= void) and then (a_string.count > 0)
from
i := 1
until
i > a_string.count or Result = false
loop
Result := Result and is_valid_character(a_string.item (i))
i := i + 1
end
end
is_valid_character (a_char: CHARACTER): BOOLEAN is
-- check if character is valid (isn't special character)
do
Result := a_char.is_alpha or a_char.is_digit or a_char.is_equal ('_')
end
feature {NONE} -- Add entry Implementation
add_entry_from_table (the_entry: DS_HASH_TABLE[STRING, STRING]) is
-- add en entry from a table generated by the xml parser
require
entry_not_void: the_entry /= void
local
the_values: ARRAY[STRING]
has_errors: BOOLEAN
i: INTEGER
do
if not the_entry.has (name) then
has_errors := true
end
if not the_entry.has (score) and then not the_entry.item (score).is_integer then
has_errors := true
end
create the_values.make(1, other_keys.count)
from
i := 1
until
i > other_keys.count or has_errors
loop
if the_entry.has (other_keys.item (i)) then
the_values.put (the_entry.item (other_keys.item (i)), i)
else
the_values.put ("", i)
end
i := i + 1
end
if not has_errors then
add_entry_extended (the_entry.item (name), the_entry.item (score).to_integer, the_values)
end
end
feature -- Sort and Crop
crop_local (nb_elements: INTEGER) is
-- crop local highscore to 'nb_elements'
require
positive: nb_elements > 0
do
local_highscore.keep_first (nb_elements.min (local_highscore.count))
end
sort_local_by_name (sort_reverse: BOOLEAN) is
-- sort local highscore by name
do
sort (local_highscore, name, sort_reverse)
end
sort_local_by_score (sort_reverse: BOOLEAN) is
-- sort local highscore by score
do
sort (local_highscore, score, sort_reverse)
end
sort_local_by_key (a_key: STRING; sort_reverse: BOOLEAN) is
-- sort local highscore by a key (alphabetical sorting)
do
sort (local_highscore, a_key, sort_reverse)
end
feature {NONE} -- Sort Implementation
sort (a_highscore: like local_highscore; a_key: STRING; is_reverse: BOOLEAN) is
-- sort the highscore (using bubble sort)
require
a_highscore /= void
key_valid (a_key) or a_key.is_equal (name) or a_key.is_equal(score)
local
changes_made: BOOLEAN
i: INTEGER
-- todo: temporary stuff only
loop_count: INTEGER
one, two: like highscore_entries
do
-- "name" and "score" are treated like special keys, which makes the feature
-- somewhat bloated. This could be split up in several features.
from
changes_made := true
loop_count := 0
until
not changes_made
loop
from
i := a_highscore.count
changes_made := false
until
i <= 1
loop
one := a_highscore.item(i)
two := a_highscore.item (i - 1)
if is_reverse then
if a_key.is_equal (name) then
if a_highscore.item (i).name < a_highscore.item (i - 1).name then
a_highscore.swap (i, i - 1)
changes_made := true
end
elseif a_key.is_equal (score) then
if a_highscore.item (i).score < a_highscore.item (i - 1).score then
a_highscore.swap (i, i - 1)
changes_made := true
end
else
if a_highscore.item (i).less_than(a_highscore.item(i - 1), a_key) then
a_highscore.swap (i, i - 1)
changes_made := true
end
end
else
if a_key.is_equal (name) then
if a_highscore.item (i - 1).name < a_highscore.item (i).name then
a_highscore.swap (i, i - 1)
changes_made := true
end
elseif a_key.is_equal (score) then
if a_highscore.item (i - 1).score < a_highscore.item (i).score then
a_highscore.swap (i, i - 1)
changes_made := true
end
else
if a_highscore.item (i - 1).less_than(a_highscore.item(i), a_key) then
a_highscore.swap (i, i - 1)
changes_made := true
end
end
end
i := i - 1
end
loop_count := loop_count + 1
end
debug
io.put_string ("Sorting required " + loop_count.out + " loops%N")
end
end
feature -- Access
highscore_file: PLAIN_TEXT_FILE
highscore_dir: KL_DIRECTORY
local_highscore: DS_LINKED_LIST[like highscore_entries]
-- data structure with local highscore in it
num_of_local_entries: INTEGER
-- desired number of entries in local highscore
feature -- Checksum
checksum_generator: EM_CHECKSUM_GENERATOR
set_checksum_generator (a_generator: like checksum_generator) is
-- set the checksum generator
-- (if you want to use your own checksum generator)
do
checksum_generator := a_generator
-- tell the xml-filter to use this checksum generator
highscore_checksum_filter.set_checksum_generator (a_generator)
end
feature {NONE} -- Attributes
xml_section: STRING is "highscore"
name: STRING is "name"
score: STRING is "score"
feature -- Keys
other_keys: ARRAY[STRING]
key_valid(the_key: STRING): BOOLEAN is
-- check if a key is valid
require
key_not_void: the_key /= void
do
Result := other_keys.has (the_key)
end
has_keywords (the_keys: ARRAY[STRING]): BOOLEAN is
-- check if an array has a keyword
require
the_keys /= void
do
the_keys.compare_objects
Result := the_keys.has (name) or the_keys.has (score)
end
feature {NONE} -- Client ID
client_id: INTEGER
generate_client_id: INTEGER is
-- generate a random client id
local
rnd: RANDOM
do
create rnd.make
rnd.set_seed (time.ticks \\ client_id.max_value)
Result := rnd.i_th (1)
end
feature {NONE} -- Attributes Implementation
highscore_parser: XM_EIFFEL_PARSER
highscore_xm_filter: EM_HIGHSCORE_XM_CALLBACKS_FILTER
highscore_checksum_filter: EM_XM_CHECKSUM_FILTER
highscore_entries: EM_HIGHSCORE_ENTRY
-- dummy element
end