Merge lp:~rschroll/euchre/refactor into lp:euchre
- refactor
- Merge into trunk
Status: | Needs review |
---|---|
Proposed branch: | lp:~rschroll/euchre/refactor |
Merge into: | lp:euchre |
Diff against target: |
1073 lines (+467/-433) 3 files modified
ai_basic.js (+337/-0) main.qml (+30/-433) util.js (+100/-0) |
To merge this branch: | bzr merge lp:~rschroll/euchre/refactor |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Robert Schroll (community) | Needs Fixing | ||
Euchre development team | Pending | ||
Review via email: mp+252521@code.launchpad.net |
Commit message
Description of the change
I'm interested in trying to improve the AI a little bit. But I wanted to clean up a few bits of the code before attacking that.
The first commit moves all of the card images into a separate directory. This cleans up the directory view in Qt Creator, which will be nice if we start having more source files.
The second commit moves the AI code into a separate library. Each of the AIs is now encapsulated as an object, which should prove helpful if and when we try to keep track of what each player should know. As part of this, some utility code gets split off into its own library, so both the QML file and the AI library can use it. Since everyone needs to know what trump is, that's saved in the utility library. This may not be the best idea; perhaps there should be a game state object that contains this and other information.
- 93. By Robert Schroll
-
Clear hands before redealing
Usually the hands are empty as a result of play, but this isn't the case
if no one called on the previous deal.
Robert Ancell (robert-ancell) wrote : | # |
Any progress on this? It might be easier to split into a couple of MPs.
Unmerged revisions
- 93. By Robert Schroll
-
Clear hands before redealing
Usually the hands are empty as a result of play, but this isn't the case
if no one called on the previous deal. - 92. By Robert Schroll
-
Split AI logic into separate library
This should help us in improving the AI. This requires some utility
functions used by the AIs and the main logic to be split into a utility
library. We also store the trump suit there, which may or may not be a
good idea. - 91. By Robert Schroll
-
Move card images into separate directory
Preview Diff
1 | === added file 'ai_basic.js' |
2 | --- ai_basic.js 1970-01-01 00:00:00 +0000 |
3 | +++ ai_basic.js 2015-03-11 05:38:33 +0000 |
4 | @@ -0,0 +1,337 @@ |
5 | +/* |
6 | + * Copyright (C) 2013 Robert Ancell <robert.ancell@gmail.com> |
7 | + * |
8 | + * This program is free software: you can redistribute it and/or modify it under |
9 | + * the terms of the GNU General Public License as published by the Free Software |
10 | + * Foundation, either version 3 of the License, or (at your option) any later |
11 | + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the |
12 | + * license. |
13 | + */ |
14 | + |
15 | +.pragma library |
16 | +.import "util.js" as Util |
17 | + |
18 | +function AI (hand) { |
19 | + this.hand = hand |
20 | +} |
21 | + |
22 | +// Get an estimate on the number of tricks we will win |
23 | +AI.prototype.get_hand_strength = function (trump_suit) { |
24 | + var hand = this.hand |
25 | + function have_card (number, suit) { |
26 | + for (var i = 0; i < hand.length; i++) |
27 | + if (hand[i].number == number && hand[i].suit == suit) |
28 | + return true |
29 | + return false |
30 | + } |
31 | + function suit_count (suit) { |
32 | + var n = 0 |
33 | + for (var i = 0; i < hand.length; i++) { |
34 | + var s = hand[i].suit |
35 | + if (hand[i].number == "J" && hand[i].suit == Util.get_off_suit (trump_suit)) |
36 | + s = trump_suit |
37 | + if (s == suit) |
38 | + n++ |
39 | + } |
40 | + return n |
41 | + } |
42 | + |
43 | + // FIXME: Should take into account if a card has been turned down (i.e. treat King offsuit as Ace offsuit if Ace was discarded) |
44 | + |
45 | + // Count how many consecutive top trumps we have |
46 | + var n_top_trumps = 0 |
47 | + if (have_card ("J", trump_suit)) { |
48 | + n_top_trumps++ |
49 | + if (have_card ("J", Util.get_off_suit (trump_suit))) { |
50 | + n_top_trumps++ |
51 | + if (have_card ("A", trump_suit)) { |
52 | + n_top_trumps++ |
53 | + if (have_card ("K", trump_suit)) { |
54 | + n_top_trumps++ |
55 | + if (have_card ("Q", trump_suit)) |
56 | + n_top_trumps++ |
57 | + } |
58 | + } |
59 | + } |
60 | + } |
61 | + |
62 | + // Might get something with other trumps |
63 | + var n_other_trumps = suit_count (trump_suit) - n_top_trumps |
64 | + |
65 | + // Count how many Ace off-suits we have |
66 | + var n_ace_offsuits = 0 |
67 | + if (trump_suit != "♠" && have_card ("A", "♠")) |
68 | + n_ace_offsuits++ |
69 | + if (trump_suit != "♣" && have_card ("A", "♣")) |
70 | + n_ace_offsuits++ |
71 | + if (trump_suit != "♥" && have_card ("A", "♥")) |
72 | + n_ace_offsuits++ |
73 | + if (trump_suit != "♦" && have_card ("A", "♦")) |
74 | + n_ace_offsuits++ |
75 | + |
76 | + // Kings and queens might give something if there are other cards to help |
77 | + var n_king_offsuits = 0 |
78 | + if (trump_suit != "♠" && have_card ("K", "♠") && suit_count ("♠") > 1) |
79 | + n_king_offsuits++ |
80 | + if (trump_suit != "♣" && have_card ("K", "♣") && suit_count ("♣") > 1) |
81 | + n_king_offsuits++ |
82 | + if (trump_suit != "♥" && have_card ("K", "♥") && suit_count ("♥") > 1) |
83 | + n_king_offsuits++ |
84 | + if (trump_suit != "♦" && have_card ("K", "♦") && suit_count ("♦") > 1) |
85 | + n_king_offsuits++ |
86 | + var n_queen_offsuits = 0 |
87 | + if (trump_suit != "♠" && have_card ("Q", "♠") && suit_count ("♠") > 2) |
88 | + n_queen_offsuits++ |
89 | + if (trump_suit != "♣" && have_card ("Q", "♣") && suit_count ("♣") > 2) |
90 | + n_queen_offsuits++ |
91 | + if (trump_suit != "♥" && have_card ("Q", "♥") && suit_count ("♥") > 2) |
92 | + n_queen_offsuits++ |
93 | + if (trump_suit != "♦" && have_card ("Q", "♦") && suit_count ("♦") > 2) |
94 | + n_queen_offsuits++ |
95 | + |
96 | + // FIXME: Should simulate and find the best values for these weightings |
97 | + return n_top_trumps + n_other_trumps * 0.5 + n_ace_offsuits + n_king_offsuits * 0.5 + n_queen_offsuits * 0.5 |
98 | +} |
99 | + |
100 | +// Return true if the AI will order the dealer |
101 | +// dealer is number of players to dealer from self |
102 | +AI.prototype.will_order = function (dealer, kitty_card) { |
103 | + // If the dealer is our partner then expect more from then |
104 | + // FIXME: Should simulate and find the best values for these weightings |
105 | + var partner_n = 0.5 |
106 | + if (dealer == 2) { |
107 | + if (kitty_card.number == "J") |
108 | + partner_n = 1.5 |
109 | + else |
110 | + partner_n = 0.75 |
111 | + } |
112 | + |
113 | + // Work out how strong we are with this trump |
114 | + var n |
115 | + var swap |
116 | + var swap_index |
117 | + if (dealer == 0) { |
118 | + swap = this.pick_swap (kitty_card) |
119 | + if (swap != undefined) { |
120 | + swap_index = this.hand.indexOf (swap) |
121 | + this.hand[swap_index] = kitty_card |
122 | + } |
123 | + } |
124 | + n = this.get_hand_strength (kitty_card.suit) |
125 | + if (swap != undefined) |
126 | + this.hand[swap_index] = swap |
127 | + |
128 | + // Order if we think we can win this round |
129 | + return n + partner_n >= 3 |
130 | +} |
131 | + |
132 | +// Pick a card to swap when ordered |
133 | +AI.prototype.pick_swap = function (kitty_card) { |
134 | + var swap_card |
135 | + var swap_strength = -1 |
136 | + for (var i = 0; i < this.hand.length; i++) { |
137 | + // Temporarily swap card |
138 | + var t = this.hand[i] |
139 | + this.hand[i] = kitty_card |
140 | + |
141 | + var s = this.get_hand_strength (kitty_card.suit) |
142 | + if (s > swap_strength) { |
143 | + swap_card = t |
144 | + swap_strength = s |
145 | + } |
146 | + |
147 | + // Swap card back |
148 | + this.hand[i] = t |
149 | + } |
150 | + |
151 | + return swap_card |
152 | +} |
153 | + |
154 | +// Pick a trump suit to play as or "" to pass |
155 | +AI.prototype.pick_trump = function (player) { |
156 | + // Try each suit and pick the best one |
157 | + var trump_suit = "" |
158 | + var best_n = 0 |
159 | + var n = this.get_hand_strength ("♠") |
160 | + if (n > best_n) { |
161 | + trump_suit = "♠" |
162 | + best_n = n |
163 | + } |
164 | + var n = this.get_hand_strength ("♣") |
165 | + if (n > best_n) { |
166 | + trump_suit = "♣" |
167 | + best_n = n |
168 | + } |
169 | + var n = this.get_hand_strength ("♥") |
170 | + if (n > best_n) { |
171 | + trump_suit = "♥" |
172 | + best_n = n |
173 | + } |
174 | + var n = this.get_hand_strength ("♦") |
175 | + if (n > best_n) { |
176 | + trump_suit = "♦" |
177 | + best_n = n |
178 | + } |
179 | + |
180 | + // FIXME: Should take into account what their opponent has done |
181 | + |
182 | + // Pick one we think can win with |
183 | + var partner_n = 0.5 |
184 | + if (best_n + partner_n >= 3) |
185 | + return trump_suit |
186 | + else |
187 | + return "" |
188 | +} |
189 | + |
190 | +// Pick a card for the AI to play |
191 | +AI.prototype.pick_card = function (trick) { |
192 | + // Lead with the highest off-suit, or trump if that's all you have! |
193 | + if (trick.length == 0) { |
194 | + // FIXME: Take into account which cards have been played, i.e. King might be the highest |
195 | + // FIXME: Pick between suits based on if they have been led, and if you can de-suit |
196 | + var highest_trump |
197 | + var highest_trump_value = -1 |
198 | + var highest_non_trump |
199 | + var highest_non_trump_value = -1 |
200 | + for (var i = 0; i < this.hand.length; i++) { |
201 | + var card = this.hand[i] |
202 | + var v = Util.get_card_value (card) |
203 | + if (Util.is_trump (card)) { |
204 | + if (v > highest_trump_value) { |
205 | + highest_trump = card |
206 | + highest_trump_value = v |
207 | + } |
208 | + } |
209 | + else { |
210 | + if (v > highest_non_trump_value) { |
211 | + highest_non_trump = card |
212 | + highest_non_trump_value = v |
213 | + } |
214 | + } |
215 | + } |
216 | + if (highest_non_trump != undefined) |
217 | + return highest_non_trump |
218 | + else |
219 | + return highest_trump |
220 | + } |
221 | + |
222 | + // Work out which cards are playable (follow suit or anything) |
223 | + var playable_cards = [] |
224 | + var lead_suit = Util.get_suit (trick[0]) |
225 | + for (var i = 0; i < this.hand.length; i++) |
226 | + if (Util.get_suit (this.hand[i]) == lead_suit) |
227 | + playable_cards.push (this.hand[i]) |
228 | + if (playable_cards.length == 0) |
229 | + playable_cards = this.hand |
230 | + |
231 | + var winning_card = Util.get_winning_card (trick) |
232 | + var winning_card_value = Util.get_card_value (winning_card) |
233 | + |
234 | + // Work out various useful cards we can play |
235 | + var lowest_in_suit |
236 | + var lowest_in_suit_value = 8 |
237 | + var lowest_winning |
238 | + var lowest_winning_value = 8 |
239 | + var highest_winning |
240 | + var highest_winning_value = -1 |
241 | + var lowest_trump |
242 | + var lowest_trump_value = 8 |
243 | + var lowest_non_trump |
244 | + var lowest_non_trump_value = 8 |
245 | + for (var i = 0; i < playable_cards.length; i++) { |
246 | + var card = playable_cards[i] |
247 | + var v = Util.get_card_value (card) |
248 | + |
249 | + if (Util.get_suit (card) == lead_suit) { |
250 | + if (v < lowest_in_suit_value) { |
251 | + lowest_in_suit = card |
252 | + lowest_in_suit_value = v |
253 | + } |
254 | + } |
255 | + |
256 | + if (Util.get_suit (card) == Util.get_suit (winning_card) && v > winning_card_value) { |
257 | + if (v < lowest_winning_value) { |
258 | + lowest_winning = card |
259 | + lowest_winning_value = v |
260 | + } |
261 | + if (v > highest_winning_value) { |
262 | + highest_winning = card |
263 | + highest_winning_value = v |
264 | + } |
265 | + } |
266 | + |
267 | + if (Util.is_trump (card)) { |
268 | + if (v < lowest_trump_value) { |
269 | + lowest_trump = card |
270 | + lowest_trump_value = v |
271 | + } |
272 | + } |
273 | + else { |
274 | + if (v < lowest_non_trump_value) { |
275 | + lowest_non_trump = card |
276 | + lowest_non_trump_value = v |
277 | + } |
278 | + } |
279 | + } |
280 | + |
281 | + // Can only trump if we can't follow suit |
282 | + var lowest_winning_trump |
283 | + if (lowest_in_suit == undefined && !Util.is_trump (winning_card)) |
284 | + lowest_winning_trump = lowest_trump |
285 | + |
286 | + // Get the card we should throw away if can't do anything |
287 | + // FIXME: Should try to de-suit |
288 | + var discard = lowest_non_trump |
289 | + if (discard == undefined) |
290 | + discard = lowest_trump |
291 | + |
292 | + // Following opponent - play highest card in that suit or lowest trump |
293 | + // FIXME: Shouldn't play a high card if we think it will be more useful later |
294 | + if (trick.length == 1) { |
295 | + if (highest_winning != undefined) |
296 | + return highest_winning |
297 | + else if (lowest_winning_trump != undefined) |
298 | + return lowest_winning_trump |
299 | + else |
300 | + return discard |
301 | + } |
302 | + |
303 | + // Following partner, win if they haven't, otherwise play lowest card |
304 | + if (trick.length == 2) { |
305 | + // FIXME: They might be winning but need us to play higher |
306 | + var partner_won = trick[0] == winning_card |
307 | + if (partner_won) { |
308 | + if (lowest_in_suit != undefined) |
309 | + return lowest_in_suit |
310 | + else |
311 | + return discard |
312 | + } |
313 | + else { |
314 | + if (highest_winning != undefined) |
315 | + return highest_winning |
316 | + else if (lowest_winning_trump != undefined) |
317 | + return lowest_winning_trump |
318 | + else |
319 | + return discard |
320 | + } |
321 | + } |
322 | + |
323 | + // Ending trick, if partner is winning play lowest card, otherwise win with with lowest possible card |
324 | + if (trick.length == 3) { |
325 | + var partner_won = trick[1] == winning_card |
326 | + if (partner_won) { |
327 | + if (lowest_in_suit != undefined) |
328 | + return lowest_in_suit |
329 | + else |
330 | + return discard |
331 | + } |
332 | + else { |
333 | + if (lowest_winning != undefined) |
334 | + return lowest_winning |
335 | + if (lowest_winning_trump != undefined) |
336 | + return lowest_winning_trump |
337 | + else |
338 | + return discard |
339 | + } |
340 | + } |
341 | +} |
342 | |
343 | === added directory 'cards' |
344 | === renamed file 'aceClubs.png' => 'cards/aceClubs.png' |
345 | === renamed file 'aceDiamonds.png' => 'cards/aceDiamonds.png' |
346 | === renamed file 'aceHearts.png' => 'cards/aceHearts.png' |
347 | === renamed file 'aceSpades.png' => 'cards/aceSpades.png' |
348 | === renamed file 'back.png' => 'cards/back.png' |
349 | === renamed file 'jackClubs.png' => 'cards/jackClubs.png' |
350 | === renamed file 'jackDiamonds.png' => 'cards/jackDiamonds.png' |
351 | === renamed file 'jackHearts.png' => 'cards/jackHearts.png' |
352 | === renamed file 'jackSpades.png' => 'cards/jackSpades.png' |
353 | === renamed file 'kingClubs.png' => 'cards/kingClubs.png' |
354 | === renamed file 'kingDiamonds.png' => 'cards/kingDiamonds.png' |
355 | === renamed file 'kingHearts.png' => 'cards/kingHearts.png' |
356 | === renamed file 'kingSpades.png' => 'cards/kingSpades.png' |
357 | === renamed file 'nineClubs.png' => 'cards/nineClubs.png' |
358 | === renamed file 'nineDiamonds.png' => 'cards/nineDiamonds.png' |
359 | === renamed file 'nineHearts.png' => 'cards/nineHearts.png' |
360 | === renamed file 'nineSpades.png' => 'cards/nineSpades.png' |
361 | === renamed file 'queenClubs.png' => 'cards/queenClubs.png' |
362 | === renamed file 'queenDiamonds.png' => 'cards/queenDiamonds.png' |
363 | === renamed file 'queenHearts.png' => 'cards/queenHearts.png' |
364 | === renamed file 'queenSpades.png' => 'cards/queenSpades.png' |
365 | === renamed file 'tenClubs.png' => 'cards/tenClubs.png' |
366 | === renamed file 'tenDiamonds.png' => 'cards/tenDiamonds.png' |
367 | === renamed file 'tenHearts.png' => 'cards/tenHearts.png' |
368 | === renamed file 'tenSpades.png' => 'cards/tenSpades.png' |
369 | === modified file 'main.qml' |
370 | --- main.qml 2015-03-10 06:45:40 +0000 |
371 | +++ main.qml 2015-03-11 05:38:33 +0000 |
372 | @@ -10,6 +10,8 @@ |
373 | |
374 | import QtQuick 2.0 |
375 | import Ubuntu.Components 1.1 |
376 | +import "ai_basic.js" as AI |
377 | +import "util.js" as Util |
378 | |
379 | MainView { |
380 | applicationName: "com.ubuntu.developer.robert-ancell.euchre" |
381 | @@ -152,8 +154,8 @@ |
382 | var card = card_component.createObject (table) |
383 | card.number = number_symbols[n] |
384 | card.suit = suit_symbols[s] |
385 | - card.front_source = numbers[n] + suits[s] + ".png" |
386 | - card.back_source = "back.png" |
387 | + card.front_source = "cards/" + numbers[n] + suits[s] + ".png" |
388 | + card.back_source = "cards/back.png" |
389 | deck.push (card) |
390 | } |
391 | } |
392 | @@ -164,13 +166,13 @@ |
393 | var card = card_component.createObject (table) |
394 | card.opacity = 0 |
395 | card.suit = suit_symbols[s] |
396 | - card.front_source = "ace" + suits[s] + ".png" |
397 | + card.front_source = "cards/ace" + suits[s] + ".png" |
398 | suit_cards.push (card) |
399 | } |
400 | var trumpSkip = card_component.createObject (table) |
401 | trumpSkip.opacity = 0 |
402 | trumpSkip.suit = "" |
403 | - trumpSkip.front_source = "back.png" |
404 | + trumpSkip.front_source = "cards/back.png" |
405 | suit_cards.push (trumpSkip) |
406 | |
407 | // Handle the user clicking on the cards |
408 | @@ -212,6 +214,9 @@ |
409 | // The current trick |
410 | var trick = [] |
411 | |
412 | + // AIs. The first entry is null, since that corresponds to the human player |
413 | + var AIs = [ null, new AI.AI(w_hand), new AI.AI(n_hand), new AI.AI(e_hand) ] |
414 | + |
415 | // Player who dealt this round |
416 | var dealer = 0 |
417 | |
418 | @@ -224,9 +229,6 @@ |
419 | // The player that won the call |
420 | var calling_player = -1 |
421 | |
422 | - // Trump suit for this trick |
423 | - var trump_suit = "" |
424 | - |
425 | // Game scores |
426 | var ew_score = 0 |
427 | var ns_score = 0 |
428 | @@ -650,7 +652,7 @@ |
429 | current_player = dealer |
430 | lead_player = 0 |
431 | calling_player = -1 |
432 | - trump_suit = "" |
433 | + Util.trump_suit = "" |
434 | trump_label.opacity = 0 |
435 | maker_arrow.opacity = 0 |
436 | |
437 | @@ -667,13 +669,17 @@ |
438 | } |
439 | shuffle (deck) |
440 | |
441 | - n_hand = [ deck[0], deck[1], deck[2], deck[3], deck[4] ] |
442 | + n_hand.length = 0 |
443 | + n_hand.push (deck[0], deck[1], deck[2], deck[3], deck[4]) |
444 | n_tricks = [] |
445 | - e_hand = [ deck[5], deck[6], deck[7], deck[8], deck[9] ] |
446 | + e_hand.length = 0 |
447 | + e_hand.push (deck[5], deck[6], deck[7], deck[8], deck[9]) |
448 | e_tricks = [] |
449 | - s_hand = [ deck[10], deck[11], deck[12], deck[13], deck[14] ] |
450 | + s_hand.length = 0 |
451 | + s_hand.push (deck[10], deck[11], deck[12], deck[13], deck[14]) |
452 | s_tricks = [] |
453 | - w_hand = [ deck[15], deck[16], deck[17], deck[18], deck[19] ] |
454 | + w_hand.length = 0 |
455 | + w_hand.push (deck[15], deck[16], deck[17], deck[18], deck[19]) |
456 | w_tricks = [] |
457 | kitty = [ deck[20], deck[21], deck[22], deck[23] ] |
458 | trick = [] |
459 | @@ -754,9 +760,9 @@ |
460 | } |
461 | |
462 | calling_player = current_player |
463 | - trump_suit = kitty[3].suit |
464 | + Util.trump_suit = kitty[3].suit |
465 | trump_label.opacity = 0.25 |
466 | - trump_label.text = trump_suit |
467 | + trump_label.text = Util.trump_suit |
468 | maker_arrow.opacity = 0.25 |
469 | maker_arrow.rotation = 90 * (calling_player + 1) |
470 | |
471 | @@ -777,7 +783,7 @@ |
472 | |
473 | // Swap cards |
474 | console.log (player_name (dealer) + " swaps") |
475 | - var card = ai_pick_swap (dealer) |
476 | + var card = AIs[dealer].pick_swap (kitty[3]) |
477 | if (card != undefined) { |
478 | var hand = player_hand (dealer) |
479 | var hand_index = hand.indexOf (card) |
480 | @@ -844,11 +850,11 @@ |
481 | trump_label.opacity = 0.25 |
482 | trump_label.text = suit |
483 | calling_player = current_player |
484 | - trump_suit = suit |
485 | + Util.trump_suit = suit |
486 | |
487 | // Raise the chosen card to the top and fade out the others |
488 | for (var i = 0; i < suit_cards.length; i++) { |
489 | - if (suit_cards[i].suit == trump_suit) { |
490 | + if (suit_cards[i].suit == Util.trump_suit) { |
491 | var t = suit_cards[i].z |
492 | suit_cards[i].z = suit_cards[suit_cards.length - 1].z |
493 | suit_cards[suit_cards.length - 1].z = t |
494 | @@ -890,329 +896,6 @@ |
495 | layout () |
496 | } |
497 | |
498 | - // Get an estimate on the number of tricks we will win |
499 | - function ai_get_hand_strength (player, trump_suit) { |
500 | - var hand = player_hand (player) |
501 | - function have_card (number, suit) { |
502 | - for (var i = 0; i < hand.length; i++) |
503 | - if (hand[i].number == number && hand[i].suit == suit) |
504 | - return true |
505 | - return false |
506 | - } |
507 | - function suit_count (suit) { |
508 | - var n = 0 |
509 | - for (var i = 0; i < hand.length; i++) { |
510 | - var s = hand[i].suit |
511 | - if (hand[i].number == "J" && hand[i].suit == get_off_suit (trump_suit)) |
512 | - s = trump_suit |
513 | - if (s == suit) |
514 | - n++ |
515 | - } |
516 | - return n |
517 | - } |
518 | - |
519 | - // FIXME: Should take into account if a card has been turned down (i.e. treat King offsuit as Ace offsuit if Ace was discarded) |
520 | - |
521 | - // Count how many consecutive top trumps we have |
522 | - var n_top_trumps = 0 |
523 | - if (have_card ("J", trump_suit)) { |
524 | - n_top_trumps++ |
525 | - if (have_card ("J", get_off_suit (trump_suit))) { |
526 | - n_top_trumps++ |
527 | - if (have_card ("A", trump_suit)) { |
528 | - n_top_trumps++ |
529 | - if (have_card ("K", trump_suit)) { |
530 | - n_top_trumps++ |
531 | - if (have_card ("Q", trump_suit)) |
532 | - n_top_trumps++ |
533 | - } |
534 | - } |
535 | - } |
536 | - } |
537 | - |
538 | - // Might get something with other trumps |
539 | - var n_other_trumps = suit_count (trump_suit) - n_top_trumps |
540 | - |
541 | - // Count how many Ace off-suits we have |
542 | - var n_ace_offsuits = 0 |
543 | - if (trump_suit != "♠" && have_card ("A", "♠")) |
544 | - n_ace_offsuits++ |
545 | - if (trump_suit != "♣" && have_card ("A", "♣")) |
546 | - n_ace_offsuits++ |
547 | - if (trump_suit != "♥" && have_card ("A", "♥")) |
548 | - n_ace_offsuits++ |
549 | - if (trump_suit != "♦" && have_card ("A", "♦")) |
550 | - n_ace_offsuits++ |
551 | - |
552 | - // Kings and queens might give something if there are other cards to help |
553 | - var n_king_offsuits = 0 |
554 | - if (trump_suit != "♠" && have_card ("K", "♠") && suit_count ("♠") > 1) |
555 | - n_king_offsuits++ |
556 | - if (trump_suit != "♣" && have_card ("K", "♣") && suit_count ("♣") > 1) |
557 | - n_king_offsuits++ |
558 | - if (trump_suit != "♥" && have_card ("K", "♥") && suit_count ("♥") > 1) |
559 | - n_king_offsuits++ |
560 | - if (trump_suit != "♦" && have_card ("K", "♦") && suit_count ("♦") > 1) |
561 | - n_king_offsuits++ |
562 | - var n_queen_offsuits = 0 |
563 | - if (trump_suit != "♠" && have_card ("Q", "♠") && suit_count ("♠") > 2) |
564 | - n_queen_offsuits++ |
565 | - if (trump_suit != "♣" && have_card ("Q", "♣") && suit_count ("♣") > 2) |
566 | - n_queen_offsuits++ |
567 | - if (trump_suit != "♥" && have_card ("Q", "♥") && suit_count ("♥") > 2) |
568 | - n_queen_offsuits++ |
569 | - if (trump_suit != "♦" && have_card ("Q", "♦") && suit_count ("♦") > 2) |
570 | - n_queen_offsuits++ |
571 | - |
572 | - // FIXME: Should simulate and find the best values for these weightings |
573 | - return n_top_trumps + n_other_trumps * 0.5 + n_ace_offsuits + n_king_offsuits * 0.5 + n_queen_offsuits * 0.5 |
574 | - } |
575 | - |
576 | - // Return true if the AI will order the dealer |
577 | - function ai_will_order (player) { |
578 | - // If the dealer is our partner then expect more from then |
579 | - // FIXME: Should simulate and find the best values for these weightings |
580 | - var partner_n = 0.5 |
581 | - if (player == (dealer + 2) % 4) { |
582 | - if (kitty[3].number == "J") |
583 | - partner_n = 1.5 |
584 | - else |
585 | - partner_n = 0.75 |
586 | - } |
587 | - |
588 | - // Work out how strong we are with this trump |
589 | - var hand = player_hand (player) |
590 | - var n |
591 | - var swap |
592 | - var swap_index |
593 | - if (player == dealer) { |
594 | - swap = ai_pick_swap (player) |
595 | - if (swap != undefined) { |
596 | - swap_index = hand.indexOf (swap) |
597 | - hand[swap_index] = kitty[3] |
598 | - } |
599 | - } |
600 | - n = ai_get_hand_strength (player, kitty[3].suit) |
601 | - if (swap != undefined) |
602 | - hand[swap_index] = swap |
603 | - |
604 | - // Order if we think we can win this round |
605 | - return n + partner_n >= 3 |
606 | - } |
607 | - |
608 | - // Pick a card to swap when ordered |
609 | - function ai_pick_swap (player) { |
610 | - var hand = player_hand (player) |
611 | - var swap_card |
612 | - var swap_strength = -1 |
613 | - for (var i = 0; i < hand.length; i++) { |
614 | - // Temporarily swap card |
615 | - var t = hand[i] |
616 | - hand[i] = kitty[3] |
617 | - |
618 | - var s = ai_get_hand_strength (player, "", kitty[3].suit) |
619 | - if (s > swap_strength) { |
620 | - swap_card = t |
621 | - swap_strength = s |
622 | - } |
623 | - |
624 | - // Swap card back |
625 | - hand[i] = t |
626 | - } |
627 | - |
628 | - return swap_card |
629 | - } |
630 | - |
631 | - // Pick a trump suit to play as or "" to pass |
632 | - function ai_pick_trump (player) { |
633 | - // Try each suit and pick the best one |
634 | - var trump_suit = "" |
635 | - var best_n = 0 |
636 | - var n = ai_get_hand_strength (player, "♠") |
637 | - if (n > best_n) { |
638 | - trump_suit = "♠" |
639 | - best_n = n |
640 | - } |
641 | - var n = ai_get_hand_strength (player, "♣") |
642 | - if (n > best_n) { |
643 | - trump_suit = "♣" |
644 | - best_n = n |
645 | - } |
646 | - var n = ai_get_hand_strength (player, "♥") |
647 | - if (n > best_n) { |
648 | - trump_suit = "♥" |
649 | - best_n = n |
650 | - } |
651 | - var n = ai_get_hand_strength (player, "♦") |
652 | - if (n > best_n) { |
653 | - trump_suit = "♦" |
654 | - best_n = n |
655 | - } |
656 | - |
657 | - // FIXME: Should take into account what their opponent has done |
658 | - |
659 | - // Pick one we think can win with |
660 | - var partner_n = 0.5 |
661 | - if (best_n + partner_n >= 3) |
662 | - return trump_suit |
663 | - else |
664 | - return "" |
665 | - } |
666 | - |
667 | - // Pick a card for the AI to play |
668 | - function ai_pick_card (player) { |
669 | - var hand = player_hand (player) |
670 | - |
671 | - // Lead with the highest off-suit, or trump if that's all you have! |
672 | - if (trick.length == 0) { |
673 | - // FIXME: Take into account which cards have been played, i.e. King might be the highest |
674 | - // FIXME: Pick between suits based on if they have been led, and if you can de-suit |
675 | - var highest_trump |
676 | - var highest_trump_value = -1 |
677 | - var highest_non_trump |
678 | - var highest_non_trump_value = -1 |
679 | - for (var i = 0; i < hand.length; i++) { |
680 | - var card = hand[i] |
681 | - var v = get_card_value (card) |
682 | - if (is_trump (card)) { |
683 | - if (v > highest_trump_value) { |
684 | - highest_trump = card |
685 | - highest_trump_value = v |
686 | - } |
687 | - } |
688 | - else { |
689 | - if (v > highest_non_trump_value) { |
690 | - highest_non_trump = card |
691 | - highest_non_trump_value = v |
692 | - } |
693 | - } |
694 | - } |
695 | - if (highest_non_trump != undefined) |
696 | - return highest_non_trump |
697 | - else |
698 | - return highest_trump |
699 | - } |
700 | - |
701 | - // Work out which cards are playable (follow suit or anything) |
702 | - var playable_cards = [] |
703 | - var lead_suit = get_suit (trick[0]) |
704 | - for (var i = 0; i < hand.length; i++) |
705 | - if (get_suit (hand[i]) == lead_suit) |
706 | - playable_cards.push (hand[i]) |
707 | - if (playable_cards.length == 0) |
708 | - playable_cards = hand |
709 | - |
710 | - var winning_card = get_winning_card () |
711 | - var winning_card_value = get_card_value (winning_card) |
712 | - |
713 | - // Work out various useful cards we can play |
714 | - var lowest_in_suit |
715 | - var lowest_in_suit_value = 8 |
716 | - var lowest_winning |
717 | - var lowest_winning_value = 8 |
718 | - var highest_winning |
719 | - var highest_winning_value = -1 |
720 | - var lowest_trump |
721 | - var lowest_trump_value = 8 |
722 | - var lowest_non_trump |
723 | - var lowest_non_trump_value = 8 |
724 | - for (var i = 0; i < playable_cards.length; i++) { |
725 | - var card = playable_cards[i] |
726 | - var v = get_card_value (card) |
727 | - |
728 | - if (get_suit (card) == lead_suit) { |
729 | - if (v < lowest_in_suit_value) { |
730 | - lowest_in_suit = card |
731 | - lowest_in_suit_value = v |
732 | - } |
733 | - } |
734 | - |
735 | - if (get_suit (card) == get_suit (winning_card) && v > winning_card_value) { |
736 | - if (v < lowest_winning_value) { |
737 | - lowest_winning = card |
738 | - lowest_winning_value = v |
739 | - } |
740 | - if (v > highest_winning_value) { |
741 | - highest_winning = card |
742 | - highest_winning_value = v |
743 | - } |
744 | - } |
745 | - |
746 | - if (is_trump (card)) { |
747 | - if (v < lowest_trump_value) { |
748 | - lowest_trump = card |
749 | - lowest_trump_value = v |
750 | - } |
751 | - } |
752 | - else { |
753 | - if (v < lowest_non_trump_value) { |
754 | - lowest_non_trump = card |
755 | - lowest_non_trump_value = v |
756 | - } |
757 | - } |
758 | - } |
759 | - |
760 | - // Can only trump if we can't follow suit |
761 | - var lowest_winning_trump |
762 | - if (lowest_in_suit == undefined && !is_trump (winning_card)) |
763 | - lowest_winning_trump = lowest_trump |
764 | - |
765 | - // Get the card we should throw away if can't do anything |
766 | - // FIXME: Should try to de-suit |
767 | - var discard = lowest_non_trump |
768 | - if (discard == undefined) |
769 | - discard = lowest_trump |
770 | - |
771 | - // Following opponent - play highest card in that suit or lowest trump |
772 | - // FIXME: Shouldn't play a high card if we think it will be more useful later |
773 | - if (trick.length == 1) { |
774 | - if (highest_winning != undefined) |
775 | - return highest_winning |
776 | - else if (lowest_winning_trump != undefined) |
777 | - return lowest_winning_trump |
778 | - else |
779 | - return discard |
780 | - } |
781 | - |
782 | - // Following partner, win if they haven't, otherwise play lowest card |
783 | - if (trick.length == 2) { |
784 | - // FIXME: They might be winning but need us to play higher |
785 | - var partner_won = trick[0] == winning_card |
786 | - if (partner_won) { |
787 | - if (lowest_in_suit != undefined) |
788 | - return lowest_in_suit |
789 | - else |
790 | - return discard |
791 | - } |
792 | - else { |
793 | - if (highest_winning != undefined) |
794 | - return highest_winning |
795 | - else if (lowest_winning_trump != undefined) |
796 | - return lowest_winning_trump |
797 | - else |
798 | - return discard |
799 | - } |
800 | - } |
801 | - |
802 | - // Ending trick, if partner is winning play lowest card, otherwise win with with lowest possible card |
803 | - if (trick.length == 3) { |
804 | - var partner_won = trick[1] == winning_card |
805 | - if (partner_won) { |
806 | - if (lowest_in_suit != undefined) |
807 | - return lowest_in_suit |
808 | - else |
809 | - return discard |
810 | - } |
811 | - else { |
812 | - if (lowest_winning != undefined) |
813 | - return lowest_winning |
814 | - if (lowest_winning_trump != undefined) |
815 | - return lowest_winning_trump |
816 | - else |
817 | - return discard |
818 | - } |
819 | - } |
820 | - } |
821 | |
822 | // Play the given card |
823 | function play_card (card) { |
824 | @@ -1414,9 +1097,9 @@ |
825 | // Must follow suit |
826 | var playable_cards = [] |
827 | if (trick.length > 0) { |
828 | - var lead_suit = get_suit (trick[0]) |
829 | + var lead_suit = Util.get_suit (trick[0]) |
830 | for (var i = 0; i < s_hand.length; i++) |
831 | - if (get_suit (s_hand[i]) == lead_suit) |
832 | + if (Util.get_suit (s_hand[i]) == lead_suit) |
833 | playable_cards.push (s_hand[i]) |
834 | } |
835 | if (playable_cards.length > 0 && playable_cards.indexOf (card) < 0) { |
836 | @@ -1457,64 +1140,6 @@ |
837 | set_trump (suit) |
838 | } |
839 | |
840 | - // Get the suit that contains the left bower |
841 | - function get_off_suit (suit) { |
842 | - if (suit == "♠") |
843 | - return "♣" |
844 | - if (suit == "♣") |
845 | - return "♠" |
846 | - if (suit == "♥") |
847 | - return "♦" |
848 | - if (suit == "♦") |
849 | - return "♥" |
850 | - } |
851 | - |
852 | - // Get the suit of a card taking into account bowers |
853 | - function get_suit (card) { |
854 | - if (card.suit == get_off_suit (trump_suit) && card.number == "J") |
855 | - return trump_suit |
856 | - return card.suit |
857 | - } |
858 | - |
859 | - // Return true if the card has the given suit |
860 | - function is_suit (card, suit) { |
861 | - return get_suit (card) == suit |
862 | - } |
863 | - |
864 | - // Return true if the card is a trump |
865 | - function is_trump (card) { |
866 | - return get_suit (card) == trump_suit |
867 | - } |
868 | - |
869 | - // Get the value of a card within it's own suit |
870 | - function get_card_value (card) { |
871 | - if (card.number == "9") |
872 | - return 0 |
873 | - if (card.number == "10") |
874 | - return 1 |
875 | - if (card.number == "J") |
876 | - { |
877 | - // Right bower |
878 | - if (card.suit == trump_suit) |
879 | - return 7 |
880 | - |
881 | - // Left bower |
882 | - if (card.suit == get_off_suit (trump_suit)) |
883 | - return 6 |
884 | - |
885 | - // Plain old Jack |
886 | - return 2 |
887 | - } |
888 | - if (card.number == "Q") |
889 | - return 3 |
890 | - if (card.number == "K") |
891 | - return 4 |
892 | - if (card.number == "A") |
893 | - return 5 |
894 | - |
895 | - return -1 |
896 | - } |
897 | - |
898 | // Start the next turn |
899 | function next_turn () { |
900 | current_player = (current_player + 1) % 4 |
901 | @@ -1548,7 +1173,8 @@ |
902 | |
903 | // AI chooses if it wants to order the dealer |
904 | phase = "ordering" |
905 | - if (ai_will_order (current_player)) |
906 | + // Ensure argument is positive, since % is broken in javascript |
907 | + if (AIs[current_player].will_order ((4 + dealer - current_player) % 4, kitty[3])) |
908 | order_dealer () |
909 | else |
910 | pass_order () |
911 | @@ -1576,7 +1202,7 @@ |
912 | |
913 | // AI chooses if it wants to order the dealer |
914 | phase = "calling" |
915 | - var suit = ai_pick_trump (current_player) |
916 | + var suit = AIs[current_player].pick_trump () |
917 | set_trump (suit) |
918 | } |
919 | card.moved.connect (moved) |
920 | @@ -1586,38 +1212,9 @@ |
921 | } |
922 | } |
923 | |
924 | - // Get the winning card from the current trick |
925 | - function get_winning_card () { |
926 | - function compare_card (card0, card1, lead_suit) { |
927 | - if (get_suit (card0) != get_suit (card1)) |
928 | - { |
929 | - // Trumps beat non-trumps |
930 | - if (is_suit (card0, trump_suit)) |
931 | - return 1 |
932 | - if (is_suit (card1, trump_suit)) |
933 | - return -1 |
934 | - |
935 | - // Lead suit beats off-suit |
936 | - if (is_suit (card0, lead_suit)) |
937 | - return 1 |
938 | - if (is_suit (card1, lead_suit)) |
939 | - return -1 |
940 | - } |
941 | - |
942 | - return get_card_value (card0) - get_card_value (card1) |
943 | - } |
944 | - var lead_suit = trick[0].suit |
945 | - var best_card = 0 |
946 | - for (var i = 1; i < trick.length; i++) |
947 | - if (compare_card (trick[i], trick[best_card], lead_suit, trump_suit) > 0) |
948 | - best_card = i |
949 | - |
950 | - return trick[best_card] |
951 | - } |
952 | - |
953 | // Get the player currently winning the trick |
954 | function trick_winner () { |
955 | - var card = get_winning_card () |
956 | + var card = Util.get_winning_card (trick) |
957 | return (lead_player + trick.indexOf (card)) % 4 |
958 | } |
959 | |
960 | @@ -1630,7 +1227,7 @@ |
961 | } |
962 | else { |
963 | // FIXME: Pretend to be thinking |
964 | - var card = ai_pick_card (current_player) |
965 | + var card = AIs[current_player].pick_card (trick) |
966 | console.log (player_name (current_player) + " plays " + card.number + card.suit) |
967 | play_card (card) |
968 | var hand = player_hand (current_player) |
969 | |
970 | === added file 'util.js' |
971 | --- util.js 1970-01-01 00:00:00 +0000 |
972 | +++ util.js 2015-03-11 05:38:33 +0000 |
973 | @@ -0,0 +1,100 @@ |
974 | +/* |
975 | + * Copyright (C) 2013 Robert Ancell <robert.ancell@gmail.com> |
976 | + * |
977 | + * This program is free software: you can redistribute it and/or modify it under |
978 | + * the terms of the GNU General Public License as published by the Free Software |
979 | + * Foundation, either version 3 of the License, or (at your option) any later |
980 | + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the |
981 | + * license. |
982 | + */ |
983 | + |
984 | +.pragma library |
985 | + |
986 | +var trump_suit |
987 | + |
988 | +// Get the suit that contains the left bower |
989 | +function get_off_suit (suit) { |
990 | + if (suit == "♠") |
991 | + return "♣" |
992 | + if (suit == "♣") |
993 | + return "♠" |
994 | + if (suit == "♥") |
995 | + return "♦" |
996 | + if (suit == "♦") |
997 | + return "♥" |
998 | +} |
999 | + |
1000 | +// Get the suit of a card taking into account bowers |
1001 | +function get_suit (card) { |
1002 | + if (card.suit == get_off_suit (trump_suit) && card.number == "J") |
1003 | + return trump_suit |
1004 | + return card.suit |
1005 | +} |
1006 | + |
1007 | +// Return true if the card has the given suit |
1008 | +function is_suit (card, suit) { |
1009 | + return get_suit (card) == suit |
1010 | +} |
1011 | + |
1012 | +// Return true if the card is a trump |
1013 | +function is_trump (card) { |
1014 | + return get_suit (card) == trump_suit |
1015 | +} |
1016 | + |
1017 | +// Get the value of a card within it's own suit |
1018 | +function get_card_value (card) { |
1019 | + if (card.number == "9") |
1020 | + return 0 |
1021 | + if (card.number == "10") |
1022 | + return 1 |
1023 | + if (card.number == "J") |
1024 | + { |
1025 | + // Right bower |
1026 | + if (card.suit == trump_suit) |
1027 | + return 7 |
1028 | + |
1029 | + // Left bower |
1030 | + if (card.suit == get_off_suit (trump_suit)) |
1031 | + return 6 |
1032 | + |
1033 | + // Plain old Jack |
1034 | + return 2 |
1035 | + } |
1036 | + if (card.number == "Q") |
1037 | + return 3 |
1038 | + if (card.number == "K") |
1039 | + return 4 |
1040 | + if (card.number == "A") |
1041 | + return 5 |
1042 | + |
1043 | + return -1 |
1044 | +} |
1045 | + |
1046 | +// Get the winning card from the current trick |
1047 | +function get_winning_card (trick) { |
1048 | + function compare_card (card0, card1, lead_suit) { |
1049 | + if (get_suit (card0) != get_suit (card1)) |
1050 | + { |
1051 | + // Trumps beat non-trumps |
1052 | + if (is_suit (card0, trump_suit)) |
1053 | + return 1 |
1054 | + if (is_suit (card1, trump_suit)) |
1055 | + return -1 |
1056 | + |
1057 | + // Lead suit beats off-suit |
1058 | + if (is_suit (card0, lead_suit)) |
1059 | + return 1 |
1060 | + if (is_suit (card1, lead_suit)) |
1061 | + return -1 |
1062 | + } |
1063 | + |
1064 | + return get_card_value (card0) - get_card_value (card1) |
1065 | + } |
1066 | + var lead_suit = trick[0].suit |
1067 | + var best_card = 0 |
1068 | + for (var i = 1; i < trick.length; i++) |
1069 | + if (compare_card (trick[i], trick[best_card], lead_suit, trump_suit) > 0) |
1070 | + best_card = i |
1071 | + |
1072 | + return trick[best_card] |
1073 | +} |
This is broken when everyone passes twice. Please ignore this for now while I track that down.