maxorator

Tic-tac-toe for VC:MP

Jun 14th, 2015
204
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 21.80 KB | None | 0 0
  1. CHALLENGE_TIMEOUT <- 30;
  2.  
  3. class TTTChallenge {
  4. challenger = null;
  5. challengee = null;
  6. startTime = 0;
  7.  
  8. constructor (challenger, challengee, startTime) {
  9. this.challenger = challenger;
  10. this.challengee = challengee;
  11. this.startTime = startTime;
  12. }
  13. }
  14.  
  15. class TTTChallengeManager {
  16. challenges = null;
  17. main = null;
  18.  
  19. constructor(main) {
  20. this.main = main;
  21. this.challenges = {};
  22. }
  23.  
  24. function MakeChallenge(challenger, challengee) {
  25. if (main.gameManager.IsPlaying(challenger)) {
  26. ::ClientMessage("You are currently in the middle of a game.", challenger, 255, 255, 255);
  27. return;
  28. } else if (challenger.ID == challengee.ID) {
  29. ::ClientMessage("You cannot challenge yourself.", challenger, 255, 255, 255);
  30. return;
  31. } else if (main.gameManager.IsPlaying(challengee)) {
  32. ::ClientMessage("That player is currently in the middle of a game.", challenger, 255, 255, 255);
  33. return;
  34. } else if (challenger.ID in challenges) {
  35. if (challenges[challenger.ID].challengee.ID == challengee.ID) {
  36. ::ClientMessage("Already sent a challenge to that player.", challenger, 255, 255, 255);
  37. return;
  38. } else {
  39. WithdrawChallenge(challenger);
  40. }
  41. }
  42.  
  43. challenges[challenger.ID] <- TTTChallenge(challenger, challengee, ::time());
  44. ::ClientMessage("You have challenged " + challengee.Name + " to a game of tic-tac-toe.", challenger, 255, 255, 255);
  45. ::ClientMessage(challenger.Name + " has challenged you to a game of tic-tac-toe. Enter '/accept " + challenger.ID + "' to accept.", challengee, 255, 255, 255);
  46. }
  47.  
  48. function AcceptChallenge(challengee, challenger) {
  49. if (!(challenger.ID in challenges) || challenges[challenger.ID].challengee.ID != challengee.ID) {
  50. ::ClientMessage("That player has not challenged you.", challengee, 255, 255, 255);
  51. return;
  52. }
  53.  
  54. local game = main.gameManager.CreateGame(challenger, challengee);
  55.  
  56. if (game == null) {
  57. ::ClientMessage(challengee.Name + " tried to accept your challenge, but slots are full at the moment.", challenger, 255, 255, 255);
  58. ::ClientMessage("You tried to accept " + challenger.Name + "'s challenge, but slots are full at the moment.", challengee, 255, 255, 255);
  59. } else {
  60. ::ClientMessage(challengee.Name + " has accepted your challenge.", challenger, 255, 255, 255);
  61. ::ClientMessage("You have accepted " + challenger.Name + "'s challenge.", challengee, 255, 255, 255);
  62.  
  63. delete challenges[challenger.ID];
  64.  
  65. game.Begin();
  66. }
  67. }
  68.  
  69. function WithdrawChallenge(challenger) {
  70. if (challenger.ID in challenges) {
  71. delete challenges[challenger.ID];
  72.  
  73. ::ClientMessage("You have withdrawn your challenge.", challenger, 255, 255, 255);
  74. ::ClientMessage(challenger.Name + " has withdrawn his challenge to you.", challengee, 255, 255, 255);
  75. } else {
  76. ::ClientMessage("You have not challenged anyone.", challenger, 255, 255, 255);
  77. }
  78. }
  79.  
  80. function RemoveExpiredChallenges() {
  81. local deleteList = array(0);
  82. local currentTime = ::time();
  83.  
  84. foreach (challengerId, challenge in challenges) {
  85. if (challenge.startTime + CHALLENGE_TIMEOUT < currentTime) {
  86. deleteList.append(challengerId);
  87. ::ClientMessage("Your challenge has expired.", challenge.challenger, 255, 255, 255);
  88. ::ClientMessage(challenge.challenger.Name + "'s challenge to you has expired.", challenge.challengee, 255, 255, 255);
  89. }
  90. }
  91.  
  92. DeleteChallenges(deleteList)
  93. }
  94.  
  95. function DeleteChallenges(deleteList) {
  96. foreach (challengerId in deleteList) {
  97. delete challenges[challengerId];
  98. }
  99. }
  100.  
  101. function PlayerQuit(player) {
  102. local deleteList = array(0);
  103.  
  104. foreach (challengerId, challenge in challenges) {
  105. if (challenge.challengee.ID == player.ID) {
  106. ::ClientMessage("Your challengee has quit the game.", challenge.challenger, 255, 255, 255);
  107. deleteList.append(challengerId);
  108. } else if (challenge.challenger.ID == player.ID) {
  109. ::ClientMessage(challenge.challenger.Name + "'s challenge to you has been cancelled.", challenge.challengee, 255, 255, 255);
  110. deleteList.append(challengerId);
  111. }
  112. }
  113.  
  114. DeleteChallenges(deleteList);
  115. }
  116. }
  117.  
  118. GAME_SLOTS <- 3;
  119. BETTING_TIME <- 20;
  120. MOVE_MAX_TIME <- 15;
  121. GAME_DISAPPEAR_TIME <- 5;
  122.  
  123. class TTTGame {
  124. players = null;
  125. pots = null;
  126. board = null;
  127. slot = -1;
  128. bettingStage = false;
  129. startTime = 0;
  130. lastMoveTime = 0;
  131. turn = 1;
  132. turnCount = 0;
  133. endTime = 0;
  134. grid = null;
  135. gridSprites = null;
  136. potInfo = null;
  137. potFree = null;
  138. labels = null;
  139.  
  140. static winPatterns = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]];
  141.  
  142. constructor(challenger, challengee, slot) {
  143. this.players = [challenger, challengee];
  144. this.pots = [0, 0];
  145. this.board = null;
  146. this.slot = slot;
  147. this.bettingStage = true;
  148. this.startTime = 0;
  149. this.lastMoveTime = 0;
  150. this.turn = 1;
  151. this.turnCount = 0;
  152. this.endTime = 0;
  153. this.grid = array(9, -1);
  154. this.gridSprites = array(9, null);
  155. this.potInfo = [{}, {}];
  156. this.potFree = [0, 0];
  157. this.labels = [null, null];
  158. }
  159.  
  160. function HasPlayer(player) {
  161. return player.ID == players[0].ID || player.ID == players[1].ID;
  162. }
  163.  
  164. function Begin() {
  165. board = ::CreateSprite("board.png", 520 + 320 * slot, 420, 0, 0, 0, 255);
  166. board.ShowForAll();
  167.  
  168. bettingStage = true;
  169. startTime = ::time();
  170.  
  171. ::ClientMessageToAll(players[0].Name + " and " + players[1].Name + " will start playing tic-tac-toe in " + BETTING_TIME + " seconds.", 255, 255, 255);
  172. ::ClientMessageToAll("Type '/bet " + slot + " A <sum>' to bet for " + players[0].Name + " or '/bet " + slot + " B <sum>' to bet for " + players[1].Name + ".", 255, 255, 255);
  173.  
  174. UpdateLabels();
  175. }
  176.  
  177. function AddBet(player, side, sum) {
  178. if (!bettingStage) {
  179. ::ClientMessage("Betting for this game is over.", player, 255, 255, 255);
  180. } else if (sum <= 0) {
  181. ::ClientMessage("You have to bet at least $1.", player, 255, 255, 255);
  182. } else if (player.Money < sum) {
  183. ::ClientMessage("You don't have that much money on you.", player, 255, 255, 255);
  184. } else {
  185. ::ClientMessage("You bet $" + sum + " on " + players[side].Name + ".", player, 255, 255, 255);
  186.  
  187. player.GiveMoney(-sum);
  188.  
  189. pots[side] += sum;
  190.  
  191. if (!(player.ID in potInfo[side])) {
  192. potInfo[side][player.ID] <- 0;
  193. }
  194.  
  195. potInfo[side][player.ID] += sum;
  196.  
  197. UpdateLabels();
  198. }
  199. }
  200.  
  201. function DistributePots(winner) {
  202. local winSide = (winner == null) ? -1 : (winner.ID == players[0].ID ? 0 : 1);
  203.  
  204. if (winSide == -1) {
  205. local moneyBack = {};
  206.  
  207. for (local i = 0; i < 2; i++) {
  208. foreach (playerId, sum in potInfo[i]) {
  209. if (!(playerId in moneyBack)) {
  210. moneyBack[playerId] <- 0;
  211. }
  212.  
  213. moneyBack[playerId] += sum;
  214. }
  215. }
  216.  
  217. foreach (playerId, sum in moneyBack) {
  218. local player = FindPlayer(playerId);
  219.  
  220. ::ClientMessage("You received your money ($" + sum + ") back due to a draw (" + players[0].Name + " vs + " + players[1].Name + ").", player, 255, 255, 255);
  221. player.GiveMoney(sum);
  222. }
  223.  
  224. local share = potFree[0] + potFree[1];
  225.  
  226. if (share > 0) {
  227. for (local i = 0; i < 2; i++) {
  228. ::ClientMessage("You receive half the money ($" + share + ") of bets by disconnected players.", players[i], 255, 255, 255);
  229. players[i].GiveMoney(share);
  230. }
  231. }
  232. } else {
  233. local ownedPots = pots[winSide] - potFree[winSide];
  234. local allMoney = pots[0] + pots[1];
  235.  
  236. if (ownedPots == 0) {
  237. if (allMoney > 0) {
  238. ::ClientMessage("You receive all betted money ($" + allMoney + ") since no connected player bet for you.", players[winSide], 255, 255, 255);
  239. players[winSide].GiveMoney(allMoney);
  240. }
  241. } else {
  242. local winnerMoney = (0.05 * allMoney.tofloat()).tointeger();
  243.  
  244. ::ClientMessage("As the winner, you receive 5% of betted money ($" + winnerMoney + ").", players[winSide], 255, 255, 255);
  245. players[winSide].GiveMoney(winnerMoney);
  246.  
  247. local sharedMoney = (0.95 * allMoney.tofloat()).tointeger();
  248. local fraction = {};
  249.  
  250. foreach (playerId, sum in potInfo[winSide]) {
  251. local fraction = sum.tofloat() / ownedPots.tofloat();
  252. local winnings = (sharedMoney.tofloat() * fraction).tointeger();
  253. local player = FindPlayer(playerId);
  254.  
  255. ::ClientMessage("You receive $" + winnings + " in winnings (" + players[0].Name + " vs " + players[1].Name + ").", player, 255, 255, 255);
  256. player.GiveMoney(winnings);
  257. }
  258. }
  259. }
  260. }
  261.  
  262. function UnlinkPlayerBets(player) {
  263. for (local i = 0; i < 2; i++) {
  264. if (player.ID in potInfo[i]) {
  265. potFree[i] += potInfo[i][player.ID];
  266. delete potInfo[i][player.ID];
  267. }
  268. }
  269. }
  270.  
  271. function UpdateLabels() {
  272. for (local i = 0; i < 2; i++) {
  273. if (labels[i] != null) {
  274. labels[i].Delete();
  275. }
  276.  
  277. labels[i] = ::CreateTextdraw((turn == i ? "> " : "") + players[i].Name + " ($" + pots[i] + ")", 520 + 320 * slot + 10, 400 + i * 260, turn == i ? 0xFFFF5555 : 0xFFFFFFFF);
  278. labels[i].ShowForAll();
  279. }
  280. }
  281.  
  282. function DrawGridSprite(cell, image) {
  283. local cellx = cell % 3
  284. local celly = cell / 3
  285.  
  286. gridSprites[cell] = ::CreateSprite(image == 0 ? "cello.png" : "cellx.png", 520 + 320 * slot + cellx * 80, 420 + celly * 80, 0, 0, 0, 255);
  287. gridSprites[cell].ShowForAll();
  288. }
  289.  
  290. function MakeTurn(player, cell) {
  291. if (bettingStage) {
  292. local timeLeft = BETTING_TIME - (::time() - startTime);
  293. ::ClientMessage("Wait till bets are over (" + timeLeft + " more seconds).", player, 255, 255, 255);
  294. } else if (endTime != 0) {
  295. ::ClientMessage("The game has ended. You will be able to challenge other players in a few seconds.", player, 255, 255, 255);
  296. } else if (players[turn].ID != player.ID) {
  297. ::ClientMessage("It is not your turn.", player, 255, 255, 255);
  298. } else if(cell < 0 || cell > 8) {
  299. ::ClientMessage("Invalid cell.", player, 255, 255, 255);
  300. } else if(grid[cell] != -1) {
  301. ::ClientMessage("That cell is not empty.", player, 255, 255, 255);
  302. } else {
  303. ::ClientMessage("You made a move.", player, 255, 255, 255);
  304.  
  305. lastMoveTime = ::time();
  306. turnCount += 1;
  307. grid[cell] = turn;
  308.  
  309. DrawGridSprite(cell, turn);
  310. CheckGameState();
  311.  
  312. turn = 1 - turn;
  313. UpdateLabels();
  314. }
  315. }
  316.  
  317. function CheckGameState() {
  318. foreach (p in winPatterns) {
  319. if (grid[p[0]] == grid[p[1]] && grid[p[0]] == grid[p[2]] && grid[p[0]] != -1) {
  320. Victory(players[grid[p[0]]]);
  321. return;
  322. }
  323. }
  324.  
  325. if (turnCount == 9) {
  326. Victory(null);
  327. }
  328. }
  329.  
  330. function Process() {
  331. local currentTime = ::time();
  332.  
  333. if (bettingStage) {
  334. if (startTime + BETTING_TIME <= currentTime) {
  335. ::ClientMessageToAll("Betting for match " + players[0].Name + " ($" + pots[0] + ") vs " + players[1].Name + " ($" + pots[1] + ") has ended. The game will begin.", 255, 255, 255);
  336.  
  337. bettingStage = false;
  338. lastMoveTime = currentTime;
  339. }
  340. } else if (endTime == 0) {
  341. if (lastMoveTime + MOVE_MAX_TIME <= currentTime) {
  342. local winner = 1 - turn;
  343.  
  344. ::ClientMessage(players[1 - winner].Name + " has forfeited the match by taking longer than " + MOVE_MAX_TIME + " seconds.", players[winner], 255, 255, 255);
  345. ::ClientMessage("You lost the game because you didn't make a move in " + MOVE_MAX_TIME + " seconds.", players[1 - winner], 255, 255, 255);
  346.  
  347. Victory(players[winner]);
  348. }
  349. } else if (endTime + GAME_DISAPPEAR_TIME <= currentTime) {
  350. CleanUp();
  351. return false;
  352. }
  353.  
  354. return true;
  355. }
  356.  
  357. function Victory(player) {
  358. endTime = ::time();
  359.  
  360. if (player == null) {
  361. ::ClientMessageToAll("The match between " + players[0].Name + " and " + players[1].Name + " has ended with a draw.", 255, 255, 255);
  362. } else {
  363. local winner = player.ID == players[0].ID ? 0 : 1;
  364.  
  365. ::ClientMessageToAll("The match between " + players[0].Name + " and " + players[1].Name + " has ended. " + players[winner].Name + " won.", 255, 255, 255);
  366. }
  367.  
  368. DistributePots(player);
  369. }
  370.  
  371. function PlayerQuit(player) {
  372. UnlinkPlayerBets(player);
  373.  
  374. for (local i = 0; i < 2; i++) {
  375. if (players[i].ID == player.ID) {
  376. ::ClientMessage(player.Name + " has forfeited the match by quitting.", players[1 - i], 255, 255, 255);
  377.  
  378. Victory(players[1 - i]);
  379. CleanUp();
  380.  
  381. return true;
  382. }
  383. }
  384.  
  385. return false;
  386. }
  387.  
  388. function CleanUp() {
  389. if (board != null) {
  390. board.Delete();
  391. board = null;
  392. }
  393.  
  394. foreach(i, sprite in gridSprites) {
  395. if (sprite != null) {
  396. sprite.Delete();
  397. gridSprites[i] = null;
  398. }
  399. }
  400.  
  401. foreach(i, label in labels) {
  402. if (label != null) {
  403. label.Delete();
  404. labels[i] = null;
  405. }
  406. }
  407. }
  408. }
  409.  
  410. class TTTGameManager {
  411. games = null;
  412. main = null;
  413. binds = null;
  414.  
  415. constructor(main) {
  416. this.main = main;
  417. this.games = array(GAME_SLOTS, null);
  418. this.binds = array(9, -1);
  419.  
  420. for(local i = 0; i < 9; i++) {
  421. this.binds[i] = ::BindKey(false, 49 + i, 0, 0);
  422. }
  423. }
  424.  
  425. function IsPlaying(player) {
  426. return GetPlayerGame(player) != null;
  427. }
  428.  
  429. function GetPlayerGame(player) {
  430. foreach (game in games) {
  431. if (game != null && game.HasPlayer(player)) {
  432. return game;
  433. }
  434. }
  435.  
  436. return null;
  437. }
  438.  
  439. function CreateGame(challenger, challengee) {
  440. foreach (i, game in games) {
  441. if (game == null) {
  442. games[i] = TTTGame(challenger, challengee, i);
  443. return games[i];
  444. }
  445. }
  446.  
  447. return null;
  448. }
  449.  
  450. function MakeBet(player, game, side, sum) {
  451. if (game < 0 || game >= GAME_SLOTS || games[game] == null) {
  452. ::ClientMessage("No such game.", player, 255, 255, 255);
  453. } else {
  454. games[game].AddBet(player, side, sum);
  455. }
  456. }
  457.  
  458. function PlayerQuit(player) {
  459. foreach (i, game in games) {
  460. if (game != null) {
  461. if (game.PlayerQuit(player)) {
  462. games[i] = null;
  463. }
  464. }
  465. }
  466. }
  467.  
  468. function MoveHandler(player, cell) {
  469. local game = GetPlayerGame(player);
  470.  
  471. if (game == null) {
  472. ::ClientMessage("Can't make a move - not in a game.", player, 255, 255, 255);
  473. } else {
  474. game.MakeTurn(player, cell);
  475. }
  476. }
  477.  
  478. function ProcessBindUp(player, bindId) {
  479. local keyId = -1;
  480.  
  481. foreach (i, id in binds) {
  482. if (bindId == id) {
  483. MoveHandler(player, i);
  484. return;
  485. }
  486. }
  487. }
  488.  
  489. function ProcessTimeouts() {
  490. foreach (i, game in games) {
  491. if (game != null) {
  492. if (!game.Process()) {
  493. games[i] = null;
  494. }
  495. }
  496. }
  497. }
  498. }
  499.  
  500. class TTTMain {
  501. challengeManager = null;
  502. gameManager = null;
  503.  
  504. constructor() {
  505. challengeManager = TTTChallengeManager(this);
  506. gameManager = TTTGameManager(this);
  507. }
  508.  
  509. static function IsValidNumArg(argument) {
  510. return argument && ::IsNum(argument);
  511. }
  512.  
  513. function GetPlayerFromArg(from, arguments) {
  514. if (!IsValidNumArg(arguments)) {
  515. ::ClientMessage("Invalid argument.", from, 255, 255, 255);
  516. return null;
  517. }
  518.  
  519. local player = FindPlayer(arguments.tointeger());
  520.  
  521. if (player == null) {
  522. ::ClientMessage("No such player.", from, 255, 255, 255);
  523. return null;
  524. }
  525.  
  526. return player;
  527. }
  528.  
  529. function ProcessCommand(player, cmd, arguments) {
  530. if (cmd == "challenge") {
  531. local challengee = GetPlayerFromArg(player, arguments);
  532.  
  533. if (challengee != null) {
  534. challengeManager.MakeChallenge(player, challengee);
  535. }
  536. } else if (cmd == "withdraw") {
  537. challengeManager.WithdrawChallenge(player);
  538. } else if (cmd == "accept") {
  539. local challenger = GetPlayerFromArg(player, arguments);
  540.  
  541. if (challenger != null) {
  542. challengeManager.AcceptChallenge(player, challenger);
  543. }
  544. } else if (cmd == "bet") {
  545. local args = split(arguments, " ");
  546.  
  547. if (args.len() < 3 || !IsValidNumArg(args[0]) || (args[1] != "A" && args[1] != "B") || !IsValidNumArg(args[2])) {
  548. ::ClientMessage("Invalid format. Use: /bet <game> <A/B> <sum>", player, 255, 255, 255);
  549. return null;
  550. }
  551.  
  552. local game = args[0].tointeger();
  553. local side = (args[1] == "A") ? 0 : 1;
  554. local sum = args[2].tointeger();
  555.  
  556. gameManager.MakeBet(player, game, side, sum);
  557. } else if (cmd == "help") {
  558. ::ClientMessage("To challenge someone to a tic-tac-toe game, use the command /challenge <playerid>.", player, 255, 255, 255);
  559. ::ClientMessage("Once challenged, a player can accept a challenge via /accept <challengerid>.", player, 255, 255, 255);
  560. ::ClientMessage("After that bets can be made via /bet <gameid> <A/B> <sum>.", player, 255, 255, 255);
  561. ::ClientMessage("You can withdraw a challenge using /withdraw command.", player, 255, 255, 255);
  562. ::ClientMessage("To make a move in the tic-tac-toe game, use the number keys on your keyboard.", player, 255, 255, 255);
  563. ::ClientMessage("The numbers on the cells of the tic-tac-toe board show which key to press.", player, 255, 255, 255);
  564. } else {
  565. return 0;
  566. }
  567.  
  568. return 1;
  569. }
  570.  
  571. function ProcessBindUp(player, bindId) {
  572. gameManager.ProcessBindUp(player, bindId);
  573. }
  574.  
  575. function CheckTimeouts() {
  576. challengeManager.RemoveExpiredChallenges();
  577. gameManager.ProcessTimeouts();
  578. }
  579.  
  580. function PlayerQuit(player, reason) {
  581. challengeManager.PlayerQuit(player);
  582. gameManager.PlayerQuit(player);
  583. }
  584. }
  585.  
  586. tttMain <- TTTMain();
  587. tickCount <- 0;
  588.  
  589. function giveEveryoneSomeMoney() {
  590. local maxPlayers = ::GetMaxPlayers();
  591.  
  592. for(local i = 0; i < maxPlayers; i++) {
  593. local player = ::FindPlayer(i);
  594.  
  595. if (player != null) {
  596. player.GiveMoney(10);
  597. }
  598. }
  599. }
  600.  
  601. function perSecond() {
  602. tttMain.CheckTimeouts();
  603.  
  604. if (tickCount++ % 5 == 0) {
  605. giveEveryoneSomeMoney();
  606. }
  607. }
  608.  
  609. function onScriptLoad() {
  610. ::NewTimer("perSecond", 1000, 0);
  611. }
  612.  
  613. function onPlayerJoin(player) {
  614. ::ClientMessage("Welcome to the tic-tac-toe world! Type /help for instructions.", player, 255, 255, 255);
  615. }
  616.  
  617. function onPlayerPart(player, reason) {
  618. tttMain.PlayerQuit(player, reason);
  619. }
  620.  
  621. function onPlayerCommand(player, cmd, arguments) {
  622. return tttMain.ProcessCommand(player, cmd, arguments);
  623. }
  624.  
  625. function onKeyDown(player, bindId) {
  626. tttMain.ProcessBindUp(player, bindId);
  627. }
Advertisement
Add Comment
Please, Sign In to add comment