Merge branch 'master' into production

This commit is contained in:
Nick Winter 2015-01-02 15:45:20 -08:00
commit c92078997f
9 changed files with 353 additions and 359 deletions

View file

@ -29,7 +29,7 @@ elementAcceptsKeystrokes = (el) ->
# not radio, checkbox, range, or color
return (tag is 'textarea' or (tag is 'input' and type in textInputTypes) or el.contentEditable in ['', 'true']) and not (el.readOnly or el.disabled)
COMMON_FILES = ['/images/pages/base/modal_background.png', '/images/level/popover_background.png'] #'/images/level/code_palette_wood_background.png', , '/images/level/code_editor_background.png'
COMMON_FILES = ['/images/pages/base/modal_background.png', '/images/level/popover_background.png', '/images/level/code_palette_wood_background.png', '/images/level/code_editor_background_border.png']
preload = (arrayOfImages) ->
$(arrayOfImages).each ->
$('<img/>')[0].src = @

View file

@ -680,55 +680,54 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
join_url_email: "Escríbenos"
join_url_hipchat: "chat público HipChat"
archmage_subscribe_desc: "Obten correos de nuevas oportunidades y anuncios."
# artisan_introduction_pref: "We must construct additional levels! People be clamoring for more content, and we can only build so many ourselves. Right now your workstation is level one; our level editor is barely usable even by its creators, so be wary. If you have visions of campaigns spanning for-loops to"
# artisan_introduction_suf: ", then this class might be for you."
# artisan_attribute_1: "Any experience in building content like this would be nice, such as using Blizzard's level editors. But not required!"
# artisan_attribute_2: "A hankering to do a whole lot of testing and iteration. To make good levels, you need to take it to others and watch them play it, and be prepared to find a lot of things to fix."
# artisan_attribute_3: "For the time being, endurance en par with an Adventurer. Our Level Editor is super preliminary and frustrating to use. You have been warned!"
# artisan_join_desc: "Use the Level Editor in these steps, give or take:"
# artisan_join_step1: "Read the documentation."
# artisan_join_step2: "Create a new level and explore existing levels."
# artisan_join_step3: "Find us in our public HipChat room for help."
# artisan_join_step4: "Post your levels on the forum for feedback."
# artisan_subscribe_desc: "Get emails on level editor updates and announcements."
# adventurer_introduction: "Let's be clear about your role: you are the tank. You're going to take heavy damage. We need people to try out brand-new levels and help identify how to make things better. The pain will be enormous; making good games is a long process and no one gets it right the first time. If you can endure and have a high constitution score, then this class might be for you."
# adventurer_attribute_1: "A thirst for learning. You want to learn how to code and we want to teach you how to code. You'll probably be doing most of the teaching in this case, though."
# adventurer_attribute_2: "Charismatic. Be gentle but articulate about what needs improving, and offer suggestions on how to improve."
# adventurer_join_pref: "Either get together with (or recruit!) an Artisan and work with them, or check the box below to receive emails when there are new levels to test. We'll also be posting about levels to review on our networks like"
# adventurer_forum_url: "our forum"
# adventurer_join_suf: "so if you prefer to be notified those ways, sign up there!"
# adventurer_subscribe_desc: "Get emails when there are new levels to test."
# scribe_introduction_pref: "CodeCombat isn't just going to be a bunch of levels. It will also include a resource for knowledge, a wiki of programming concepts that levels can hook into. That way rather than each Artisan having to describe in detail what a comparison operator is, they can simply link their level to the Article describing them that is already written for the player's edification. Something along the lines of what the "
# scribe_introduction_url_mozilla: "Mozilla Developer Network"
# scribe_introduction_suf: " has built. If your idea of fun is articulating the concepts of programming in Markdown form, then this class might be for you."
# scribe_attribute_1: "Skill in words is pretty much all you need. Not only grammar and spelling, but able to convey complicated ideas to others."
# contact_us_url: "Contact us"
# scribe_join_description: "tell us a little about yourself, your experience with programming and what sort of things you'd like to write about. We'll go from there!"
# scribe_subscribe_desc: "Get emails about article writing announcements."
# diplomat_introduction_pref: "So, if there's one thing we learned from the "
# diplomat_launch_url: "launch in October"
# diplomat_introduction_suf: "it's that there is sizeable interest in CodeCombat in other countries! We're building a corps of translators eager to turn one set of words into another set of words to get CodeCombat as accessible across the world as possible. If you like getting sneak peeks at upcoming content and getting these levels to your fellow nationals ASAP, then this class might be for you."
# diplomat_attribute_1: "Fluency in English and the language you would like to translate to. When conveying complicated ideas, it's important to have a strong grasp in both!"
# diplomat_i18n_page_prefix: "You can start translating our levels by going to our"
# diplomat_i18n_page: "translations page"
# diplomat_i18n_page_suffix: ", or our interface and website on GitHub."
# diplomat_join_pref_github: "Find your language locale file "
# diplomat_github_url: "on GitHub"
# diplomat_join_suf_github: ", edit it online, and submit a pull request. Also, check this box below to keep up-to-date on new internationalization developments!"
# diplomat_subscribe_desc: "Get emails about i18n developments and levels to translate."
# ambassador_introduction: "This is a community we're building, and you are the connections. We've got forums, emails, and social networks with lots of people to talk with and help get acquainted with the game and learn from. If you want to help people get involved and have fun, and get a good feel of the pulse of CodeCombat and where we're going, then this class might be for you."
# ambassador_attribute_1: "Communication skills. Be able to identify the problems players are having and help them solve them. Also, keep the rest of us informed about what players are saying, what they like and don't like and want more of!"
# ambassador_join_desc: "tell us a little about yourself, what you've done and what you'd be interested in doing. We'll go from there!"
# ambassador_join_note_strong: "Note"
# ambassador_join_note_desc: "One of our top priorities is to build multiplayer where players having difficulty solving levels can summon higher level wizards to help them. This will be a great way for ambassadors to do their thing. We'll keep you posted!"
# ambassador_subscribe_desc: "Get emails on support updates and multiplayer developments."
# changes_auto_save: "Changes are saved automatically when you toggle checkboxes."
# diligent_scribes: "Our Diligent Scribes:"
# powerful_archmages: "Our Powerful Archmages:"
# creative_artisans: "Our Creative Artisans:"
# brave_adventurers: "Our Brave Adventurers:"
# translating_diplomats: "Our Translating Diplomats:"
# helpful_ambassadors: "Our Helpful Ambassadors:"
artisan_introduction_suf: ", entonces esta lase es ideal para ti."
artisan_attribute_1: "Cualquier experiencia creando contenido similar estaría bien, como por ejemplo el editor de niveles de Blizzard. ¡Aunque no es necesaria!"
artisan_attribute_2: "Un anhelo de hacer un montón de pruebas e iteraciones. Para hacer buenos niveles necesitas mostrárselos a otros y mirar como juegan, además de estar preparado para encontrar los fallos a reparar."
artisan_attribute_3: "Por el momento, la resistencia va a la par con el Aventurero. Nuestro editor de niveles está a un nivel de desarrollo temprano y puede ser muy frustrante usarlo. ¡Estás advertido!"
artisan_join_desc: "Sigue las siguientes indicaciones para usar el editor de niveles. Tómalo o déjalo:"
artisan_join_step1: "Lee la documentación."
artisan_join_step2: "Crea un nuevo nivel y explora los niveles existentes."
artisan_join_step3: "Busca nuestra sala pública de HipChat en busca de ayuda."
artisan_join_step4: "Publica tus niveles en el foro para recibir comentarios críticos."
artisan_subscribe_desc: "Recibe correos sobre actualizaciones del editor de niveles y anuncios."
adventurer_introduction: "Hablemos claro sobre tu papel: eres el tanque. Vas a recibir fuertes daños. Necesitamos gente para probar nuestros flamantes niveles y ayudar a mejorarlos. El dolor será enorme; hacer buenos juegos es un proceso largo y nadie lo consigue a la primera. Si puedes resistir y tener una puntuación alta en resistencia, entonces esta clase es para ti."
adventurer_attribute_1: "Estar sediento de conocimientos. Quieres aprender a programar y nosotros queremos enseñarte a hacerlo. Aunque en este caso es más probable que seas tú el que esté haciendo la mayor parte de la enseñanza."
adventurer_attribute_2: "Carismático. Se amable pero claro a la hora de desglosar qué necesita ser mejorado y sugiere de qué formas podría hacerse."
adventurer_join_pref: "Reúnete con (¡o recluta!) un Artesano y trabaja con ellos, o marca la casilla de abajo para recibir un correo cuando haya nuevos niveles para probar. También publicaremos en nuestras redes nuevos niveles para revisar"
adventurer_forum_url: "nuestro foro"
adventurer_join_suf: "así que si prefieres estar informado en esa forma, ¡crea una cuenta allí!"
adventurer_subscribe_desc: "Recibe correos cuando haya nuevos niveles para testar."
scribe_introduction_pref: "CodeCombat no será solo un montón de niveles. También será una fuente de conocimientos, una wiki de conceptos de programación a la que los niveles se engancharan. De esa forma, en lugar de que cada Artesano tenga que describir en detalle qué es un operador de comparación, podrá simplemente enlazar el nivel al Artículo que los describe y que ya ha sido escrito para edificación del jugador. Algo en la línea de lo que la "
scribe_introduction_url_mozilla: "Mozilla Developer Network"
scribe_introduction_suf: " ha construido. Si tu idea de diversión es articular los conceptos de la programación de una forma sencilla, entonces esta clase es para ti."
scribe_attribute_1: "Habilidad a la hora de escribir es casi todo lo que necesitas. No solo dominar la gramática y la ortografía sino también expresar ideas complicadas a los demás de forma sencilla."
contact_us_url: "Escribenos un correo electrónico"
scribe_join_description: "cuéntanos más sobre ti, tu experiencia en el mundo de la programación y sobre qué cosas te gustaría escribir. ¡Y continuaremos a partir de ahí!"
scribe_subscribe_desc: "Recibe correos sobre anuncios de redacción de Artículos."
diplomat_introduction_pref: "Así, si hemos aprendido algo desde el "
diplomat_launch_url: "lanzamiento en octubre"
diplomat_introduction_suf: "hay un interés considerable en CodeCombat en otros paises, ¡especialmente Brasil! Estamos formando un cuerpo de traductores con ganas de traducir un grupo de palabras tras otro para hacer CodeCombat tan accesible para todo el mundo como sea posible. Si quieres recibir avances de próximos contenidos y quieres poner esos niveles a disposición de los que comparten tu idioma tan pronto como sea posible, entonces esta Clase es para ti."
diplomat_attribute_1: "Fluidez con el ingles y el lenguaje al que quieras traducir. Cuando de transmitir ideas complejas se trata, ¡es importante tener grandes conocimientos de ambas!"
diplomat_i18n_page_prefix: "Puedes traducir nuestros niveles yendo a nuestra"
diplomat_i18n_page: "página de traducciones"
diplomat_i18n_page_suffix: ", o en nuestra interfaz y sitio web en GitHub."
diplomat_join_pref_github: "Encuentra el fichero local de tu idioma "
diplomat_github_url: "en GitHub"
diplomat_join_suf_github: ", edítalo online, y solicita que sea revisado. Además, marca la casilla de abajo para mantenerte informado en nuevos progresos en Internacionalización."
diplomat_subscribe_desc: "Recibe correos sobre nuevos niveles y desarrollos para traducir."
ambassador_introduction: "Esta es una comunidad en construcción y tú eres parte de las conexiones. Tenemos chat Olark, correos electrónicos y las redes sociales con una gran cantidad de personas con quienes hablar, ayudar a familiarizarse con el juego y aprender. Si quieres ayudar a la gente a que se involucre, se divierta, y tenga buenas sensaciones sobre CodeCombat y hacia dónde vamos, entonces esta clase es para ti."
ambassador_attribute_1: "Habilidades de comunicación. Ser capaz de identificar los problemas que los jugadores están teniendo y ayudarles a resolverlos. Además, mantener al resto de nosotros informados sobre lo que los jugadores están diciendo, lo que les gusta, lo que no ¡y de lo que quieren más!"
ambassador_join_desc: "cuéntanos más sobre ti, que has hecho y qué estarías interesado en hacer. ¡Y continuaremos a partir de ahí!"
ambassador_join_note_strong: "Nota"
ambassador_join_note_desc: "Una de nuestras principales prioridades es construir un modo multijugador donde los jugadores con mayores dificultades a la hora de resolver un nivel, puedan invocar a los magos más avanzados para que les ayuden. Será una buena manera de que los Embajadores puedan hacer su trabajo. ¡Te mantendremos informado!"
ambassador_subscribe_desc: "Recibe correos sobre actualizaciones de soporte y desarrollo del multijugador."
changes_auto_save: "Los cambios son guardados automáticamente cuando marcas las casillas de verificación."
diligent_scribes: "Nuestros diligentes Escribas:"
powerful_archmages: "Nuestros poderosos Archimagos:"
creative_artisans: "Nuestros creativos Artesanos:"
brave_adventurers: "Nuestros bravos Aventureros:"
translating_diplomats: "Nuestros políglotas Diplomáticos:"
helpful_ambassadors: "Nuestros amables Embajadores:"
ladder:
please_login: "Por favor inicia sesión antes de jugar una partida de escalera."
@ -836,7 +835,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
cost: "Costo"
next_payment: "Próximo Pago"
card: "Tarjeta"
status_unsubscribed_active: "No estas suscripto y no se te cobrará, pero tu cuenta estar activa por ahora."
status_unsubscribed_active: "No estas suscripto y no se te cobrará, pero tu cuenta está activa por ahora."
status_unsubscribed: "Obtén acceso a nuevos niveles, heroés, items y gemas extras con la suscripción a CodeCombat!"
loading_error:
@ -866,27 +865,27 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
user_schema: "Esquema de Usuario"
user_profile: "Perfil de Usuario"
patches: "Parches"
# patched_model: "Source Document"
patched_model: "Documento fuente"
model: "Modelo"
system: "Sistema"
systems: "Sistemas"
component: "Componente"
components: "Componentes"
# thang: "Thang"
# thangs: "Thangs"
thang: "Thang"
thangs: "Thangs"
level_session: "Tu Sesión"
opponent_session: "Sesión del Oponente"
article: "Artícule"
user_names: "Nombres de usuario"
# thang_names: "Thang Names"
thang_names: "Nombres de thang"
files: "Archivos"
# top_simulators: "Top Simulators"
# source_document: "Source Document"
top_simulators: "Mejores simuladores"
source_document: "Documento fuente"
document: "Documento"
# sprite_sheet: "Sprite Sheet"
employers: "Empleadores"
candidates: "Candidatos"
# candidate_sessions: "Candidate Sessions"
candidate_sessions: "Sesión de candidato"
# user_remark: "User Remark"
# user_remarks: "User Remarks"
versions: "Versiones"
@ -894,7 +893,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
heroes: "Héroes"
achievement: "Logros"
# clas: "CLAs"
# play_counts: "Play Counts"
play_counts: "Conteo de juegos"
feedback: "Feedback"
payment_info: "Información de pago"
@ -928,12 +927,12 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
opensource_description_center: "y ayudanos si quieres! CodeCombat esta construido por docenas de proyectos open source, y los amamos. Mira "
archmage_wiki_url: "nuestra wiki de Archimago wiki"
opensource_description_suffix: "Para la lista de softwares que hacen al juego posible."
# practices_title: "Respectful Best Practices"
practices_title: "Mejores prácticas respetuosas"
# practices_description: "These are our promises to you, the player, in slightly less legalese."
privacy_title: "Privacidad"
privacy_description: "No vederemos nada sobre tu información personalWe will not sell any of your personal information."
security_title: "Seguridad"
# security_description: "We strive to keep your personal information safe. As an open source project, our site is freely open to anyone to review and improve our security systems."
security_description: "Queremos mantener tu información personal privada. Como un proyecto open source, cualquiera puede revisar y mejorar nuestros sistemas de seguridad."
email_title: "Mail"
email_description_prefix: "No te vamos a inundar de Spam. Mediante"
email_settings_url: "tus opciones de mail"

View file

@ -52,11 +52,11 @@ module.exports = nativeDescription: "Македонски", englishDescription:
play_as: "Играј како" # Ladder page
spectate: "Набљудувај" # Ladder page
players: "играчи" # Hover over a level on /play
hours_played: "часови изиграни" # Hover over a level on /play
hours_played: "изиграни часови" # Hover over a level on /play
items: "Предмети" # Tooltip on item shop button from /play
unlock: "Отклучи" # For purchasing items and heroes
confirm: "Потврди"
owned: "Во сопственост" # For items you own
owned: "Имаш" # For items you own
locked: "Заклучено"
purchasable: "Може да се купи" # For a hero you unlocked but haven't purchased
available: "Достапно"
@ -142,7 +142,7 @@ module.exports = nativeDescription: "Македонски", englishDescription:
save: "Зачувај"
publish: "Објави"
create: "Направи"
manual: "Прирачник"
manual: "Рачно"
# fork: "Fork"
play: "Играј" # When used as an action verb, like "Play next level"
retry: "Обиди се повторно"
@ -162,14 +162,14 @@ module.exports = nativeDescription: "Македонски", englishDescription:
version: "Верзија"
submitter: "Подносител"
submitted: "Поднесено"
# commit_msg: "Commit Message"
commit_msg: "Порака за поднесокот"
# review: "Review"
version_history: "Историја на верзии"
version_history_for: "Историја на верзии за: "
# select_changes: "Select two changes below to see the difference."
# undo: "Undo (Ctrl+Z)"
# redo: "Redo (Ctrl+Shift+Z)"
# play_preview: "Play preview of current level"
select_changes: "Одбери две промени подолу за да ја видиш разликата."
undo: "Врати (Ctrl+Z)"
redo: "Повтори (Ctrl+Shift+Z)"
play_preview: "Пушти преглед на моменталното ниво"
result: "Резултат"
results: "Резултати"
description: "Опис"
@ -179,17 +179,17 @@ module.exports = nativeDescription: "Македонски", englishDescription:
password: "Лозинка"
message: "Порака"
# code: "Code"
# ladder: "Ladder"
ladder: "Ранг листа"
when: "Кога"
opponent: "Противник"
# rank: "Rank"
rank: "Ранг"
# score: "Score"
# win: "Win"
# loss: "Loss"
# tie: "Tie"
# easy: "Easy"
# medium: "Medium"
# hard: "Hard"
win: "Победа"
loss: "Изгубено"
tie: "Нерешено"
easy: "Лесно"
medium: "Средно"
hard: "Тешко"
player: "Играч"
# player_level: "Level" # Like player level 5, not like level: Dungeons of Kithgard
@ -238,7 +238,7 @@ module.exports = nativeDescription: "Македонски", englishDescription:
victory_sign_up: "Направи сметка за да го зачуваш напредокот"
victory_sign_up_poke: "Сакаш да го зачуваш твојот код? Направи бесплатна сметка!"
victory_rate_the_level: "Оцени го нивото: " # Only in old-style levels.
# victory_return_to_ladder: "Return to Ladder"
victory_return_to_ladder: "Врати се кај ранг листата"
victory_play_continue: "Продолжи"
victory_saving_progress: "Напредокот се зачувува"
victory_go_home: "Оди дома" # Only in old-style levels.
@ -260,54 +260,54 @@ module.exports = nativeDescription: "Македонски", englishDescription:
# tome_see_all_methods: "See all methods you can edit" # Title text for method list selector (shown when there are multiple programmable methdos).
# tome_select_a_thang: "Select Someone for "
# tome_available_spells: "Available Spells"
# tome_your_skills: "Your Skills"
# tome_help: "Help"
tome_your_skills: "Твои вештини"
tome_help: "Помош"
# tome_current_method: "Current Method"
# hud_continue_short: "Continue"
# code_saved: "Code Saved"
# skip_tutorial: "Skip (esc)"
hud_continue_short: "Продолжи"
code_saved: "Кодот е зачуван"
skip_tutorial: "Прескокни (esc)"
# keyboard_shortcuts: "Key Shortcuts"
# loading_ready: "Ready!"
# loading_start: "Start Level"
# problem_alert_title: "Fix Your Code"
# problem_alert_help: "Help"
# time_current: "Now:"
# time_total: "Max:"
# time_goto: "Go to:"
# infinite_loop_try_again: "Try Again"
loading_ready: "Готово!"
loading_start: "Почни ниво"
problem_alert_title: "Поправи си го кодот"
problem_alert_help: "Помош"
time_current: "Сега:"
time_total: "Максимум:"
time_goto: "Оди до:"
infinite_loop_try_again: "Обиди се повторно"
# infinite_loop_reset_level: "Reset Level"
# infinite_loop_comment_out: "Comment Out My Code"
# tip_toggle_play: "Toggle play/paused with Ctrl+P."
# tip_scrub_shortcut: "Ctrl+[ and Ctrl+] rewind and fast-forward."
# tip_guide_exists: "Click the guide, inside game menu (at the top of the page), for useful info."
tip_toggle_play: "Пушти/Паузирај со Ctrl+P."
tip_scrub_shortcut: "Ctrl+[ и Ctrl+] премотуваат назад и напред."
tip_guide_exists: "Кликни на водичот, во менито (на врвот на страната), за корисни информации."
# tip_open_source: "CodeCombat is 100% open source!"
# tip_beta_launch: "CodeCombat launched its beta in October, 2013."
# tip_think_solution: "Think of the solution, not the problem."
# tip_theory_practice: "In theory, there is no difference between theory and practice. But in practice, there is. - Yogi Berra"
# tip_error_free: "There are two ways to write error-free programs; only the third one works. - Alan Perlis"
# tip_debugging_program: "If debugging is the process of removing bugs, then programming must be the process of putting them in. - Edsger W. Dijkstra"
# tip_forums: "Head over to the forums and tell us what you think!"
tip_beta_launch: "CodeCombat ја пушти својата бета верзија во октомври, 2013."
tip_think_solution: "Мисли на решението, не на проблемот."
tip_theory_practice: "Теоретски, не постои разлика помеѓу теорија и пракса. Но, во пракса, постои. - Yogi Berra"
tip_error_free: "Постојат два начина за пишување програми без грешки; само третиот работи. - Alan Perlis"
tip_debugging_program: "Ако дебагирање е процесот на отстранување грешки, тогаш програмирање мора да е процесот при кој тие настануваат. - Edsger W. Dijkstra"
tip_forums: "Упати се кон форумите и кажи ни што мислиш!"
# tip_baby_coders: "In the future, even babies will be Archmages."
# tip_morale_improves: "Loading will continue until morale improves."
tip_morale_improves: "Вчитувањето ќе продолжи се додека моралот не се подобри."
# tip_all_species: "We believe in equal opportunities to learn programming for all species."
# tip_reticulating: "Reticulating spines."
# tip_harry: "Yer a Wizard, "
# tip_great_responsibility: "With great coding skill comes great debug responsibility."
tip_great_responsibility: "Со големи програмерски вештини доаѓа и голема одговорност за дебагирање."
# tip_munchkin: "If you don't eat your vegetables, a munchkin will come after you while you're asleep."
# tip_binary: "There are only 10 types of people in the world: those who understand binary, and those who don't."
# tip_commitment_yoda: "A programmer must have the deepest commitment, the most serious mind. ~ Yoda"
# tip_no_try: "Do. Or do not. There is no try. - Yoda"
tip_binary: "Има само 10 вида на луѓе во светот: оние кои што разбираат бинарно, и оние кои не разбираат."
tip_commitment_yoda: "Програмер мора да ја има најдлабоката обврзаност, најсериозниот ум. ~ Yoda"
tip_no_try: "Направи. Или не прави. Нема обидување. - Yoda"
# tip_patience: "Patience you must have, young Padawan. - Yoda"
# tip_documented_bug: "A documented bug is not a bug; it is a feature."
# tip_impossible: "It always seems impossible until it's done. - Nelson Mandela"
# tip_talk_is_cheap: "Talk is cheap. Show me the code. - Linus Torvalds"
# tip_first_language: "The most disastrous thing that you can ever learn is your first programming language. - Alan Kay"
# tip_hardware_problem: "Q: How many programmers does it take to change a light bulb? A: None, it's a hardware problem."
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
tip_documented_bug: "Документирана грешка не е грешка; тоа е едноставно нешто што програмот го прави."
tip_impossible: "Секогаш изледа невозможно, се додека некој не го направи. - Nelson Mandela"
tip_talk_is_cheap: "Зборувањето е евтино. Покажи ми го кодот. - Linus Torvalds"
tip_first_language: "Нај катастрофалната работа што можеш да ја научиш е твојот прв програмски јазик. - Alan Kay"
tip_hardware_problem: "Прашање: Колку програмери се потребни за да се смени сијалица? Одговор: Ниту еден, тоа е хардверски проблем."
tip_hofstadters_law: "Законот на Hofstadter: Секогаш треба повеќе време отколку што очекуваш, дури и кога ќе го земеш во предвид законот на Hofstadter."
tip_premature_optimization: "Предвремената оптимизација е коренот на сето зло. - Donald Knuth"
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
# tip_superpower: "Coding is the closest thing we have to a superpower."
tip_extrapolation: "Има само два вида на луѓе: оние кои можат да екстраполираат од некомплетни податоци..."
tip_superpower: "Програмирањето е способност, најблиска до супермоќ, која ја имаме."
# game_menu:
# inventory_tab: "Inventory"
@ -339,16 +339,16 @@ module.exports = nativeDescription: "Македонски", englishDescription:
# equip: "Equip"
# unequip: "Unequip"
# buy_gems:
# few_gems: "A few gems"
# pile_gems: "Pile of gems"
# chest_gems: "Chest of gems"
# purchasing: "Purchasing..."
# declined: "Your card was declined"
# retrying: "Server error, retrying."
# prompt_title: "Not Enough Gems"
# prompt_body: "Do you want to get more?"
# prompt_button: "Enter Shop"
buy_gems:
few_gems: "Неколку скапоцени камења"
pile_gems: "Купче скапоцени камења"
chest_gems: "Сандак скапоцени камења"
purchasing: "Купувам..."
declined: "Твојата картичка беше одбиена"
retrying: "Серверска грешка, се обидувам повторно."
prompt_title: "Немаш доволно скапоцени камења"
prompt_body: "Дали сакаш да земеш повеќе?"
prompt_button: "Влези во продавницата"
# recovered: "Previous gems purchase recovered. Please refresh the page."
# subscribe:

View file

@ -22,11 +22,11 @@
right: 0
width: 75%
#analytics-button
position: absolute
right: 1%
top: 1%
padding: 3px 8px
#analytics-button
position: absolute
right: 1%
top: 1%
padding: 3px 8px
#analytics-modal .modal-content
background-color: white
#analytics-modal .modal-content
background-color: white

View file

@ -186,6 +186,9 @@ $gameControlMargin: 30px
.tooltip
z-index: 2
.tooltip-arrow
display: none
.level-info-container
display: none
position: absolute

View file

@ -75,5 +75,7 @@ block outer_content
td= campaignDropOffs.levels[i].finished
td= campaignDropOffs.levels[i].finishDropped
td= campaignDropOffs.levels[i].finishDropRate
else
button.btn.btn-default.disabled#analytics-button Analytics Loading...
block footer

View file

@ -262,7 +262,7 @@ module.exports = class CampaignEditorView extends RootView
# TODO: Why do we need this url dash?
request = @supermodel.addRequestResource 'campaign_drop_offs', {
url: '/db/analytics_log_event/-/campaign_drop_offs'
data: {startDay: startDay, slugs: [@campaignHandle]}
data: {startDay: startDay, slug: @campaignHandle}
method: 'POST'
success: success
}, 0

View file

@ -93,16 +93,30 @@ module.exports = class InventoryModal extends ModalView
locked = not (item.get('original') in me.items())
#locked = false if me.get('slug') is 'nick'
required = item.get('original') in _.flatten(_.values(@options.level.get('requiredGear') ? {}))
restricted = item.get('original') in _.flatten(_.values(@options.level.get('restrictedGear') ? {}))
allRestrictedGear = _.flatten(_.values(@options.level.get('restrictedGear') ? {}))
restricted = item.get('original') in allRestrictedGear
# TODO: make this re-use result of computation of updateLevelRequiredItems, which we can only do after heroClass is ready...
requiredToPurchase = false
if requiredGear = @options.level.get('requiredGear')
inCampaignView = $('#campaign-view').length
unless gearSlugs[item.get('original')] is 'tarnished-bronze-breastplate' and inCampaignView and @options.level.get('slug') is 'the-raised-sword'
for slot in item.getAllowedSlots()
continue unless requiredItems = requiredGear[slot]
continue if @equipment[slot] and @equipment[slot] not in allRestrictedGear
# Point out that they must buy it if they haven't bought any of the required items for that slot, and it's the first one.
if item.get('original') is requiredItems[0] and not _.find(requiredItems, (requiredItem) -> me.ownsItem requiredItem)
requiredToPurchase = true
break
placeholder = not item.getFrontFacingStats().props.length and not _.size(item.getFrontFacingStats().stats)
if placeholder and locked # The item is not complete, so don't put it into a collection.
null
else if locked and required
else if locked and requiredToPurchase
item.classes.push 'locked'
@itemGroups.requiredPurchaseItems.add item
else if locked and item.get('slug') isnt 'simple-boots'
else if locked
item.classes.push 'locked'
if item.isSilhouettedItem() or not item.get('gems')
# Don't even load/show these--don't add to a collection. (Bandwidth optimization.)
@ -363,66 +377,66 @@ module.exports = class InventoryModal extends ModalView
config
requireLevelEquipment: ->
# This is temporary, until we have a more general way of awarding items and configuring required/restricted items per level.
requiredGear = @options.level.get('requiredGear') ? {}
restrictedGear = @options.level.get('restrictedGear') ? {}
if @inserted
if @supermodel.finished()
equipment = @getCurrentEquipmentConfig() # Make sure @equipment is updated
else
equipment = @equipment
hadRequired = @remainingRequiredEquipment?.length
@remainingRequiredEquipment = []
@$el.find('.should-equip').removeClass('should-equip')
inWorldMap = $('#world-map-view').length
if @supermodel.finished() and heroClass = @selectedHero?.get('heroClass')
for slot, item of _.clone equipment
itemModel = @items.findWhere original: item
unless itemModel and heroClass in itemModel.classes
console.log 'Unequipping', itemModel.get('heroClass'), 'item', itemModel.get('name'), 'from slot due to class restrictions.'
@unequipItemFromSlot @$el.find(".item-slot[data-slot='#{slot}']")
delete equipment[slot]
for slot, items of restrictedGear
items = [items] if _.isString items
for item in items
item = gear[item] unless item.length is 24 # Temp: until migration to DB data is done
equipped = equipment[slot]
if equipped and equipped is item
console.log 'Unequipping restricted item', equipped, 'for', slot, 'before level', @options.level.get('slug')
@unequipItemFromSlot @$el.find(".item-slot[data-slot='#{slot}']")
delete equipment[slot]
for slot, items of requiredGear
items = [items] if _.isString items # Temp: until migration to arrays is done
item = items[0] # TODO: look for the last one that they own, or the first one if they don't own any.
# TODO: require them to have one of the given items, not just either the item or anything except all these exceptions.
slug = gearSlugs[item]
if item.length isnt 24 # Temp: until migration to DB data is done
[item, slug] = [gear[item], item]
#console.log 'requiring', item, slug, 'for', slot, 'and have', equipment[slot]
if (slot in ['right-hand', 'left-hand', 'head', 'torso']) and not (heroClass is 'Warrior' or
(heroClass is 'Ranger' and @options.level.get('slug') in ['swift-dagger', 'shrapnel']) or
(heroClass is 'Wizard' and @options.level.get('slug') in ['touch-of-death', 'bonemender'])) and not (slug in ['crude-builders-hammer', 'wooden-builders-hammer'])
# After they switch to a ranger or wizard, we stop being so finicky about class-specific gear.
continue
continue if slug is 'tarnished-bronze-breastplate' and inWorldMap and @options.level.get('slug') is 'the-raised-sword' # Don't tell them they need it until they need it in the level
# This is called frequently to make sure the player isn't using any restricted items and knows she must equip any required items.
return unless @inserted
equipment = if @supermodel.finished() then @getCurrentEquipmentConfig() else @equipment # Make sure we're using latest equipment.
hadRequired = @remainingRequiredEquipment?.length
@remainingRequiredEquipment = []
@$el.find('.should-equip').removeClass('should-equip')
@unequipClassRestrictedItems equipment
@unequipLevelRestrictedItems equipment
@updateLevelRequiredItems equipment
if hadRequired and not @remainingRequiredEquipment.length
@endHighlight()
@highlightElement '#play-level-button', duration: 5000
$('#play-level-button').prop('disabled', @remainingRequiredEquipment.length > 0)
unequipClassRestrictedItems: (equipment) ->
return unless @supermodel.finished() and heroClass = @selectedHero?.get 'heroClass'
for slot, item of _.clone equipment
itemModel = @items.findWhere original: item
unless itemModel and heroClass in itemModel.classes
console.log 'Unequipping', itemModel.get('heroClass'), 'item', itemModel.get('name'), 'from slot due to class restrictions.'
@unequipItemFromSlot @$el.find(".item-slot[data-slot='#{slot}']")
delete equipment[slot]
unequipLevelRestrictedItems: (equipment) ->
return unless restrictedGear = @options.level.get 'restrictedGear'
for slot, items of restrictedGear
for item in items
equipped = equipment[slot]
continue if equipped and not (
(slug is 'crude-builders-hammer' and equipped in [gear['simple-sword'], gear['long-sword'], gear['sharpened-sword'], gear['roughedge']]) or
(slug in ['simple-sword', 'long-sword', 'roughedge', 'sharpened-sword'] and equipped is gear['crude-builders-hammer']) or
(slug is 'leather-boots' and equipped is gear['simple-boots']) or
(slug is 'simple-boots' and equipped is gear['leather-boots'])
)
itemModel = @items.findWhere {slug: slug}
continue unless itemModel
availableSlotSelector = "#unequipped .item[data-item-id='#{itemModel.id}']"
@highlightElement availableSlotSelector, delay: 500, sides: ['right'], rotation: Math.PI / 2
@$el.find(availableSlotSelector).addClass 'should-equip'
@$el.find("#equipped div[data-slot='#{slot}']").addClass 'should-equip'
@remainingRequiredEquipment.push slot: slot, item: item
if hadRequired and not @remainingRequiredEquipment.length
@endHighlight()
@highlightElement '#play-level-button', duration: 5000
$('#play-level-button').prop('disabled', @remainingRequiredEquipment.length > 0)
if equipped and equipped is item
console.log 'Unequipping restricted item', equipped, 'for', slot, 'before level', @options.level.get('slug')
@unequipItemFromSlot @$el.find(".item-slot[data-slot='#{slot}']")
delete equipment[slot]
updateLevelRequiredItems: (equipment) ->
return unless requiredGear = @options.level.get 'requiredGear'
return unless heroClass = @selectedHero?.get 'heroClass'
for slot, items of requiredGear when items.length
equipped = equipment[slot]
continue if equipped in items
continue if equipped # Actually, just let them play if they have equipped anything in that slot (and we haven't unequipped it due to restrictions).
items = (item for item in items when heroClass in (@items.findWhere(original: item)?.classes ? []))
continue unless items.length # If the required items are for another class, then let's not be finicky.
# We will point out the last (best) element that they own and can use, otherwise the first (cheapest).
bestOwnedItem = _.findLast items, (item) -> me.ownsItem item
item = bestOwnedItem ? items[0]
# For the Tarnished Bronze Breastplate only, don't tell them they need it until they need it in the level, so we can show how to buy it.
slug = gearSlugs[item]
inCampaignView = $('#campaign-view').length
continue if slug is 'tarnished-bronze-breastplate' and inCampaignView and @options.level.get('slug') is 'the-raised-sword'
# Now we're definitely requiring and pointing out an item.
itemModel = @items.findWhere {original: item}
availableSlotSelector = "#unequipped .item[data-item-id='#{itemModel.id}']"
@highlightElement availableSlotSelector, delay: 500, sides: ['right'], rotation: Math.PI / 2
@$el.find(availableSlotSelector).addClass 'should-equip'
@$el.find("#equipped div[data-slot='#{slot}']").addClass 'should-equip'
@remainingRequiredEquipment.push slot: slot, item: item
null
setHero: (@selectedHero) ->
if @selectedHero.loading

View file

@ -1,4 +1,6 @@
AnalyticsLogEvent = require './AnalyticsLogEvent'
Campaign = require '../campaigns/Campaign'
Level = require '../levels/Level'
Handler = require '../commons/Handler'
log = require 'winston'
@ -103,16 +105,17 @@ class AnalyticsLogEventHandler extends Handler
# startDay - Inclusive, optional, e.g. '2014-12-14'
# endDay - Exclusive, optional, e.g. '2014-12-16'
# TODO: Read per-campaign level progression data from a legit source
# TODO: Must be a better way to organize this series of 3 database calls (campaigns, levels, analytics)
# TODO: An uncached call can take over 30s locally
# TODO: Returns all the campaigns
# TODO: Calculate overall campaign stats
# TODO: Assumes db campaign levels are in progression order. Should build this based on actual progression.
campaignSlugs = req.query.slugs or req.body.slugs
campaignSlug = req.query.slug or req.body.slug
startDay = req.query.startDay or req.body.startDay
endDay = req.query.endDay or req.body.endDay
return @sendSuccess res, [] unless campaignSlugs?
return @sendSuccess res, [] unless campaignSlug?
# Cache results for 1 day
@campaignDropOffsCache ?= {}
@ -120,178 +123,151 @@ class AnalyticsLogEventHandler extends Handler
if (new Date()) - @campaignDropOffsCachedSince > 86400 * 1000 # Dumb cache expiration
@campaignDropOffsCache = {}
@campaignDropOffsCachedSince = new Date()
cacheKey = campaignSlugs.join(',')
cacheKey = campaignSlug
cacheKey += 's' + startDay if startDay?
cacheKey += 'e' + endDay if endDay?
return @sendSuccess res, campaignDropOffs if campaignDropOffs = @campaignDropOffsCache[cacheKey]
queryParams = {$and: [{$or: [ {"event" : 'Started Level'}, {"event" : 'Saw Victory'}]}]}
queryParams["$and"].push created: {$gte: new Date(startDay + "T00:00:00.000Z")} if startDay?
queryParams["$and"].push created: {$lt: new Date(endDay + "T00:00:00.000Z")} if endDay?
calculateDropOffs = (campaigns) =>
# Calculate campaign drop off rates
# Input:
# campaigns - per-campaign dictionary of ordered level slugs
AnalyticsLogEvent.find(queryParams).select('created event properties user').exec (err, data) =>
if err? then return @sendDatabaseError res, err
queryParams = {$and: [{$or: [ {"event" : 'Started Level'}, {"event" : 'Saw Victory'}]}]}
queryParams["$and"].push created: {$gte: new Date(startDay + "T00:00:00.000Z")} if startDay?
queryParams["$and"].push created: {$lt: new Date(endDay + "T00:00:00.000Z")} if endDay?
# Bucketize events by user
userProgression = {}
for item in data
created = item.get('created')
event = item.get('event')
if event is 'Saw Victory'
level = item.get('properties.level').toLowerCase().replace new RegExp(' ', 'g'), '-'
else
level = item.get('properties.levelID')
continue unless level?
user = item.get('user')
userProgression[user] ?= []
userProgression[user].push
created: created
event: event
level: level
# Order user progression by created
for user in userProgression
userProgression[user].sort (a,b) -> if a.created < b.created then return -1 else 1
# Per-level start/drop/finish/drop
levelProgression = {}
for user of userProgression
for i in [0...userProgression[user].length]
event = userProgression[user][i].event
level = userProgression[user][i].level
levelProgression[level] ?=
started: 0
startDropped: 0
finished: 0
finishDropped: 0
if event is 'Started Level'
levelProgression[level].started++
levelProgression[level].startDropped++ if i is userProgression[user].length - 1
else if event is 'Saw Victory'
levelProgression[level].finished++
levelProgression[level].finishDropped++ if i is userProgression[user].length - 1
# Put in campaign order
campaignRates = {}
for level of levelProgression
for campaign of campaigns
if level in campaigns[campaign]
started = levelProgression[level].started
startDropped = levelProgression[level].startDropped
finished = levelProgression[level].finished
finishDropped = levelProgression[level].finishDropped
campaignRates[campaign] ?=
levels: []
# overall:
# started: 0,
# startDropped: 0,
# finished: 0,
# finishDropped: 0
campaignRates[campaign].levels.push
level: level
started: started
startDropped: startDropped
finished: finished
finishDropped: finishDropped
break
# Sort level data by campaign order
for campaign of campaignRates
campaignRates[campaign].levels.sort (a, b) ->
if campaigns[campaign].indexOf(a.level) < campaigns[campaign].indexOf(b.level) then return -1 else 1
AnalyticsLogEvent.find(queryParams).select('created event properties user').exec (err, data) =>
if err? then return @sendDatabaseError res, err
# Return all campaign data for simplicity
# Cache other individual campaigns too, since we have them
@campaignDropOffsCache[cacheKey] = campaignRates
for campaign of campaignRates
cacheKey = campaign
cacheKey += 's' + startDay if startDay?
cacheKey += 'e' + endDay if endDay?
# Bucketize events by user
userProgression = {}
for item in data
created = item.get('created')
event = item.get('event')
if event is 'Saw Victory'
level = item.get('properties.level').toLowerCase().replace new RegExp(' ', 'g'), '-'
else
level = item.get('properties.levelID')
continue unless level?
user = item.get('user')
userProgression[user] ?= []
userProgression[user].push
created: created
event: event
level: level
# Order user progression by created
for user in userProgression
userProgression[user].sort (a,b) -> if a.created < b.created then return -1 else 1
# Per-level start/drop/finish/drop
levelProgression = {}
for user of userProgression
for i in [0...userProgression[user].length]
event = userProgression[user][i].event
level = userProgression[user][i].level
levelProgression[level] ?=
started: 0
startDropped: 0
finished: 0
finishDropped: 0
if event is 'Started Level'
levelProgression[level].started++
levelProgression[level].startDropped++ if i is userProgression[user].length - 1
else if event is 'Saw Victory'
levelProgression[level].finished++
levelProgression[level].finishDropped++ if i is userProgression[user].length - 1
# Put in campaign order
campaignRates = {}
for level of levelProgression
for campaign of campaigns
if level in campaigns[campaign]
started = levelProgression[level].started
startDropped = levelProgression[level].startDropped
finished = levelProgression[level].finished
finishDropped = levelProgression[level].finishDropped
campaignRates[campaign] ?=
levels: []
# overall:
# started: 0,
# startDropped: 0,
# finished: 0,
# finishDropped: 0
campaignRates[campaign].levels.push
level: level
started: started
startDropped: startDropped
finished: finished
finishDropped: finishDropped
break
# Sort level data by campaign order
for campaign of campaignRates
campaignRates[campaign].levels.sort (a, b) ->
if campaigns[campaign].indexOf(a.level) < campaigns[campaign].indexOf(b.level) then return -1 else 1
# Return all campaign data for simplicity
# Cache other individual campaigns too, since we have them
@campaignDropOffsCache[cacheKey] = campaignRates
@sendSuccess res, campaignRates
for campaign of campaignRates
cacheKey = campaign
cacheKey += 's' + startDay if startDay?
cacheKey += 'e' + endDay if endDay?
@campaignDropOffsCache[cacheKey] = campaignRates
@sendSuccess res, campaignRates
# Copied from WorldMapView
dungeonLevels = [
'dungeons-of-kithgard',
'gems-in-the-deep',
'shadow-guard',
'kounter-kithwise',
'crawlways-of-kithgard',
'forgetful-gemsmith',
'true-names',
'favorable-odds',
'the-raised-sword',
'haunted-kithmaze',
'riddling-kithmaze',
'descending-further',
'the-second-kithmaze',
'dread-door',
'known-enemy',
'master-of-names',
'lowly-kithmen',
'closing-the-distance',
'tactical-strike',
'the-final-kithmaze',
'the-gauntlet',
'kithgard-gates',
'cavern-survival'
];
getLevelData = (campaigns, campaignLevelIDs) =>
# Get level data and replace levelIDs with level slugs in campaigns
# Input:
# campaigns - per-campaign dictionary of ordered levelIDs
# campaignLevelIDs - dictionary of all campaign levelIDs
# Output:
# campaigns - per-campaign dictionary of ordered level slugs
forestLevels = [
'defense-of-plainswood',
'winding-trail',
'patrol-buster',
'endangered-burl',
'village-guard',
'thornbush-farm',
'back-to-back',
'ogre-encampment',
'woodland-cleaver',
'shield-rush',
'peasant-protection',
'munchkin-swarm',
'munchkin-harvest',
'swift-dagger',
'shrapnel',
'arcane-ally',
'touch-of-death',
'bonemender',
'coinucopia',
'copper-meadows',
'drop-the-flag',
'deadly-pursuit',
'rich-forager',
'siege-of-stonehold',
'multiplayer-treasure-grove',
'dueling-grounds'
];
Level.find({original: {$in: campaignLevelIDs}, "version.isLatestMajor": true, "version.isLatestMinor": true}).exec (err, documents) =>
if err? then return @sendDatabaseError res, err
desertLevels = [
'the-dunes',
'the-mighty-sand-yak',
'oasis',
'sarven-road',
'sarven-gaps',
'thunderhooves',
'medical-attention',
'minesweeper',
'sarven-sentry',
'keeping-time',
'hoarding-gold',
'decoy-drill',
'yakstraction',
'sarven-brawl',
'desert-combat',
'dust',
'mirage-maker',
'sarven-savior',
'odd-sandstorm'
];
levelSlugMap = {}
for doc in documents
levelID = doc.get('original')
levelSlug = doc.get('name').toLowerCase().replace new RegExp(' ', 'g'), '-'
levelSlugMap[levelID] = levelSlug
campaigns = {
'dungeon': dungeonLevels,
'forest': forestLevels,
'desert': desertLevels
}
# Replace levelIDs with level slugs
for campaign of campaigns
mapFn = (item) -> levelSlugMap[item]
campaigns[campaign] = _.map campaigns[campaign], mapFn, @
# Forest campaign levels are reversed for some reason
campaigns[campaign].reverse() if campaign is 'forest'
calculateDropOffs campaigns
getCampaignData = () =>
# Get campaign data
# Output:
# campaigns - per-campaign dictionary of ordered levelIDs
# campaignLevelIDs - dictionary of all campaign levelIDs
Campaign.find().exec (err, documents) =>
if err? then return @sendDatabaseError res, err
campaigns = {}
levelCampaignMap = {}
campaignLevelIDs = []
for doc in documents
campaignSlug = doc.get('slug')
levels = doc.get('levels')
campaigns[campaignSlug] = []
levelCampaignMap[campaignSlug] = {}
for levelID of levels
campaigns[campaignSlug].push levelID
campaignLevelIDs.push levelID
levelCampaignMap[levelID] = campaignSlug
getLevelData campaigns, campaignLevelIDs
getCampaignData()
module.exports = new AnalyticsLogEventHandler()