Mercurial > irccd
comparison plugins/hangman/hangman.js @ 0:1158cffe5a5e
Initial import
author | David Demelier <markand@malikania.fr> |
---|---|
date | Mon, 08 Feb 2016 16:43:14 +0100 |
parents | |
children | c4fe9a8b1a62 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:1158cffe5a5e |
---|---|
1 /* | |
2 * hangman.js -- hangman game for IRC | |
3 * | |
4 * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> | |
5 * | |
6 * Permission to use, copy, modify, and/or distribute this software for any | |
7 * purpose with or without fee is hereby granted, provided that the above | |
8 * copyright notice and this permission notice appear in all copies. | |
9 * | |
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
17 */ | |
18 | |
19 /* Modules */ | |
20 var Logger = Irccd.Logger; | |
21 var File = Irccd.File; | |
22 var Plugin = Irccd.Plugin; | |
23 var Server = Irccd.Server; | |
24 var Unicode = Irccd.Unicode | |
25 var Util = Irccd.Util; | |
26 | |
27 /* Plugin information */ | |
28 info = { | |
29 author: "David Demelier <markand@malikania.fr>", | |
30 license: "ISC", | |
31 summary: "A hangman game for IRC", | |
32 version: "@IRCCD_VERSION@" | |
33 }; | |
34 | |
35 /* Default options */ | |
36 Plugin.config["collaborative"] = "true"; | |
37 | |
38 function Hangman(server, channel) | |
39 { | |
40 this.server = server; | |
41 this.channel = channel; | |
42 this.tries = 10; | |
43 this.select(); | |
44 } | |
45 | |
46 /** | |
47 * Map of games. | |
48 */ | |
49 Hangman.map = {}; | |
50 | |
51 /** | |
52 * List of words | |
53 */ | |
54 Hangman.words = []; | |
55 | |
56 /** | |
57 * Formats for writing. | |
58 */ | |
59 Hangman.formats = { | |
60 "asked": "#{nickname}, '#{letter}' was already asked.", | |
61 "dead": "#{nickname}, fail the word was: #{word}.", | |
62 "found": "#{nickname}, nice! the word is now #{word}", | |
63 "running": "#{nickname}, the game is already running.", | |
64 "start": "#{nickname}, the game is started, the word to find is: #{word}", | |
65 "win": "#{nickname}, congratulations, the word is #{word}.", | |
66 "wrong-word": "#{nickname}, this is not the word.", | |
67 "wrong-player": "#{nickname}, please wait until someone else proposes.", | |
68 "wrong-letter": "#{nickname}, there is no '#{letter}'." | |
69 }; | |
70 | |
71 /** | |
72 * Search for an existing game. | |
73 * | |
74 * @param server the server object | |
75 * @param channel the channel name | |
76 * @return the hangman instance or undefined if no one exists | |
77 */ | |
78 Hangman.find = function (server, channel) | |
79 { | |
80 return Hangman.map[server.toString() + '@' + channel]; | |
81 } | |
82 | |
83 /** | |
84 * Create a new game, store it in the map and return it. | |
85 * | |
86 * @param server the server object | |
87 * @param channel the channel name | |
88 * @return the hangman object | |
89 */ | |
90 Hangman.create = function (server, channel) | |
91 { | |
92 return Hangman.map[server.toString() + "@" + channel] = new Hangman(server, channel); | |
93 } | |
94 | |
95 /** | |
96 * Remove the specified game from the map. | |
97 * | |
98 * @param game the game to remove | |
99 */ | |
100 Hangman.remove = function (game) | |
101 { | |
102 delete Hangman.map[game.server + "@" + game.channel]; | |
103 } | |
104 | |
105 /** | |
106 * Check if the text is a valid word. | |
107 * | |
108 * @param word the word to check | |
109 * @return true if a word | |
110 */ | |
111 Hangman.isWord = function (word) | |
112 { | |
113 if (word.length === 0) | |
114 return false; | |
115 | |
116 for (var i = 0; i < word.length; ++i) | |
117 if (!Unicode.isLetter(word.charCodeAt(i))) | |
118 return false; | |
119 | |
120 return true; | |
121 } | |
122 | |
123 /** | |
124 * Load all words. | |
125 */ | |
126 Hangman.loadWords = function () | |
127 { | |
128 var path; | |
129 | |
130 /* User specified file? */ | |
131 if (Plugin.config["file"]) | |
132 path = Plugin.config["file"]; | |
133 else | |
134 path = Plugin.configPath + "words.conf"; | |
135 | |
136 try { | |
137 Logger.info("loading words..."); | |
138 | |
139 var file = new File(path, "r"); | |
140 var line; | |
141 | |
142 while ((line = file.readline()) !== undefined) | |
143 if (Hangman.isWord(line)) | |
144 Hangman.words.push(line); | |
145 } catch (e) { | |
146 throw new Error("could not open '" + path + "'"); | |
147 } | |
148 | |
149 if (Hangman.words.length === 0) | |
150 throw new Error("empty word database"); | |
151 | |
152 Logger.info("number of words in database: " + Hangman.words.length); | |
153 } | |
154 | |
155 /** | |
156 * Load all formats. | |
157 */ | |
158 Hangman.loadFormats = function () | |
159 { | |
160 for (var key in Hangman.formats) { | |
161 var optname = "format-" + key; | |
162 | |
163 if (typeof (Plugin.config[optname]) !== "string") | |
164 continue; | |
165 | |
166 if (Plugin.config[optname].length === 0) | |
167 Logger.warning("skipping empty '" + optname + "' format"); | |
168 else | |
169 Hangman.formats[key] = Plugin.config[optname]; | |
170 } | |
171 } | |
172 | |
173 /** | |
174 * Select the next word for the game. | |
175 */ | |
176 Hangman.prototype.select = function () | |
177 { | |
178 /* Reload the words if empty */ | |
179 if (!this.words || this.words.length === 0) | |
180 this.words = Hangman.words.slice(0); | |
181 | |
182 var i = Math.floor(Math.random() * this.words.length); | |
183 | |
184 this.word = this.words[i]; | |
185 this.words.splice(i, 1); | |
186 | |
187 /* Fill table */ | |
188 this.table = {}; | |
189 | |
190 for (var j = 0; j < this.word.length; ++j) | |
191 this.table[this.word.charCodeAt(j)] = false; | |
192 } | |
193 | |
194 /** | |
195 * Format the word with underscore and letters. | |
196 * | |
197 * @return the secret | |
198 */ | |
199 Hangman.prototype.formatWord = function () | |
200 { | |
201 var str = ""; | |
202 | |
203 for (var i = 0; i < this.word.length; ++i) { | |
204 var ch = this.word.charCodeAt(i); | |
205 | |
206 if (!this.table[ch]) | |
207 str += "_"; | |
208 else | |
209 str += String.fromCharCode(ch); | |
210 | |
211 if (i + 1 < this.word.length) | |
212 str += " "; | |
213 } | |
214 | |
215 return str; | |
216 } | |
217 | |
218 /** | |
219 * Propose a word or a letter. | |
220 * | |
221 * @param ch the code point or the unique word | |
222 * @param nickname the user trying | |
223 * @return the status of the game | |
224 */ | |
225 Hangman.prototype.propose = function (ch, nickname) | |
226 { | |
227 var status = "found"; | |
228 | |
229 /* Check for collaborative mode */ | |
230 if (Plugin.config["collaborative"] === "true") { | |
231 if (this.last !== undefined && this.last === nickname) | |
232 return "wrong-player"; | |
233 | |
234 this.last = nickname; | |
235 } | |
236 | |
237 if (typeof(ch) == "number") { | |
238 if (this.table[ch] === undefined) { | |
239 this.tries -= 1; | |
240 status = "wrong-letter"; | |
241 } else { | |
242 if (this.table[ch]) { | |
243 this.tries -= 1; | |
244 status = "asked"; | |
245 } else { | |
246 this.table[ch] = true; | |
247 } | |
248 } | |
249 } else { | |
250 if (this.word != ch) { | |
251 this.tries -= 1; | |
252 status = "wrong-word"; | |
253 } else { | |
254 status = "win"; | |
255 } | |
256 } | |
257 | |
258 /* Check if dead */ | |
259 if (this.tries < 0) | |
260 status = "dead"; | |
261 | |
262 /* Check if win */ | |
263 var win = true; | |
264 | |
265 for (var i = 0; i < this.word.length; ++i) { | |
266 if (!this.table[this.word.charCodeAt(i)]) { | |
267 win = false; | |
268 break; | |
269 } | |
270 } | |
271 | |
272 if (win) | |
273 status = "win"; | |
274 | |
275 return status; | |
276 } | |
277 | |
278 function onLoad() | |
279 { | |
280 Hangman.loadFormats(); | |
281 Hangman.loadWords(); | |
282 } | |
283 | |
284 onReload = onLoad; | |
285 | |
286 function propose(server, channel, origin, game, proposition) | |
287 { | |
288 var kw = { | |
289 channel: channel, | |
290 command: server.info().commandChar, | |
291 nickname: Util.splituser(origin), | |
292 origin: origin, | |
293 plugin: Plugin.info().name, | |
294 server: server | |
295 }; | |
296 | |
297 var st = game.propose(proposition, kw.nickname); | |
298 | |
299 switch (st) { | |
300 case "found": | |
301 kw.word = game.formatWord(); | |
302 server.message(channel, Util.format(Hangman.formats["found"], kw)); | |
303 break; | |
304 case "wrong-letter": | |
305 case "wrong-player": | |
306 case "wrong-word": | |
307 case "asked": | |
308 kw.letter = String.fromCharCode(proposition); | |
309 server.message(channel, Util.format(Hangman.formats[st], kw)); | |
310 break; | |
311 case "dead": | |
312 case "win": | |
313 kw.word = game.word; | |
314 server.message(channel, Util.format(Hangman.formats[st], kw)); | |
315 | |
316 /* Remove the game */ | |
317 Hangman.remove(game); | |
318 break; | |
319 default: | |
320 break; | |
321 } | |
322 } | |
323 | |
324 function onCommand(server, origin, channel, message) | |
325 { | |
326 var game = Hangman.find(server, channel); | |
327 var kw = { | |
328 channel: channel, | |
329 command: server.info().commandChar, | |
330 nickname: Util.splituser(origin), | |
331 origin: origin, | |
332 plugin: Plugin.info().name, | |
333 server: server | |
334 }; | |
335 | |
336 if (game) { | |
337 var list = message.split(" \t"); | |
338 | |
339 if (list.length === 0 || String(list[0]).length === 0) { | |
340 server.message(channel, Util.format(Hangman.formats["running"], kw)); | |
341 } else { | |
342 var word = String(list[0]); | |
343 | |
344 if (Hangman.isWord(word)) | |
345 propose(server, channel, origin, game, word); | |
346 } | |
347 } else { | |
348 game = Hangman.create(server, channel); | |
349 kw.word = game.formatWord(); | |
350 server.message(channel, Util.format(Hangman.formats["start"], kw)); | |
351 } | |
352 } | |
353 | |
354 function onMessage(server, origin, channel, message) | |
355 { | |
356 var game = Hangman.find(server, channel); | |
357 | |
358 if (!game) | |
359 return; | |
360 | |
361 if (message.length === 1 && Unicode.isLetter(message.charCodeAt(0))) | |
362 propose(server, channel, origin, game, message.charCodeAt(0)); | |
363 } |