Created
January 18, 2022 12:21
-
-
Save ph1ee/e6214105fc8a2e48b46b5d4c486aa101 to your computer and use it in GitHub Desktop.
a territory game for the yearly lucky draw
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import pdb | |
import copy | |
from tkinter import * | |
from tkinter import Tk, Canvas, Frame, BOTH, LEFT, RIGHT | |
from functools import cmp_to_key | |
from collections import namedtuple | |
import random | |
from random import choice, shuffle, sample | |
Cell = namedtuple("Cell", ["x", "y", "player"]) | |
Player = namedtuple("Player", ["name", "color", "pos", "occupied", "entries"]) | |
TOTAL_MONEY = 72000 - 7200 | |
bonus_setup = [18000, 12000, 8000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 000, 1000] | |
# assert sum(bonus_setup) == TOTAL_MONEY | |
class Model: | |
def __init__(self, pi): | |
colors = [ | |
"Salmon", | |
"Pink", | |
"Orange", | |
"Khaki", | |
"Violet", | |
"SeaGreen", | |
"Olive", | |
"Aqua", | |
"SkyBlue", | |
"Bisque", | |
"Sienna", | |
"Azure", | |
] | |
allplayer = [] | |
occupied = [] | |
shuffle(pi) | |
for nam, pos in pi: | |
clr = choice(colors) | |
colors.remove(clr) | |
if not pos: # no preset position | |
pos = (choice(range(len(pi))), choice(range(len(pi)))) | |
while pos in occupied: | |
pos = (choice(range(len(pi))), choice(range(len(pi)))) | |
allplayer.append(Player(nam, clr, pos, [], [])) | |
occupied.append(pos) | |
board = [] | |
for y in range(len(allplayer)): | |
board.append([Cell(x, y, []) for x in range(len(allplayer))]) | |
self.ui = None | |
self.board = board | |
self.round = 0 | |
self.allplayer = allplayer | |
self.players = copy.copy(allplayer) | |
def setup(self): | |
for p in self.allplayer: | |
y, x = p.pos | |
c = self.board[y][x] | |
c.player.append(p) | |
p.occupied.append(c) | |
for u, v in [ | |
(c.y - 1, c.x), | |
(c.y + 1, c.x), | |
(c.y, c.x - 1), | |
(c.y, c.x + 1), | |
]: | |
if u < 0 or u == len(self.board) or v < 0 or v == len(self.board): | |
continue | |
c = self.board[u][v] | |
if c not in p.entries and not c.player: | |
p.entries.append(c) | |
if self.ui: | |
self.ui.update() | |
def step(self): | |
if not self.players: | |
return | |
stopped = [] | |
for p in sample(self.players, len(self.players)): | |
rmlist = [] | |
for c in p.entries: | |
if c.player: | |
rmlist.append(c) | |
for c in rmlist: | |
p.entries.remove(c) | |
if not p.entries: | |
stopped.append(p) | |
print("{} GG".format(p.name)) | |
continue | |
c = choice(p.entries) | |
c.player.append(p) | |
p.occupied.append(c) | |
p.entries.remove(c) | |
assert c.player[0].name == p.name | |
for u, v in [ | |
(c.y - 1, c.x), | |
(c.y + 1, c.x), | |
(c.y, c.x - 1), | |
(c.y, c.x + 1), | |
]: | |
if u < 0 or u == len(self.board) or v < 0 or v == len(self.board): | |
continue | |
c = self.board[u][v] | |
if c not in p.entries and not c.player: | |
p.entries.append(c) | |
for p in stopped: | |
self.players.remove(p) | |
if self.ui: | |
self.ui.animate(1000 + 100 * self.round) | |
self.round += 1 | |
def set_ui(self, ui): | |
self.ui = ui | |
class Gui(Frame): | |
def __init__(self, model): | |
super().__init__() | |
self.model = model | |
self.model.set_ui(self) | |
self.initUI() | |
def compute_bonus(self, ranking): | |
countlist = [] | |
count = 0 | |
last_occupied_number = 0 | |
for i, p in enumerate(ranking): | |
if last_occupied_number == len(p.occupied): | |
count += 1 | |
else: | |
if count > 0: | |
countlist.append(count) | |
count = 1 | |
last_occupied_number = len(p.occupied) | |
if count > 0: | |
countlist.append(count) | |
bonus = [] | |
remaining = list(reversed(bonus_setup)) | |
for c in countlist: | |
accum = 0 | |
for i in range(c): | |
accum += remaining.pop() | |
avg = accum / c | |
for i in range(c): | |
bonus.append(avg) | |
assert not remaining # should use up all money | |
assert len(bonus) == len(self.model.allplayer) | |
# assert int(sum(bonus)) == TOTAL_MONEY | |
return bonus | |
def update(self): | |
self.board_canvas.delete("all") | |
padding = 16 | |
size = 30 | |
width = 2 | |
for y, row in enumerate(self.model.board): | |
for x, c in enumerate(row): | |
x0 = padding + size * c.x | |
y0 = padding + size * c.y | |
self.board_canvas.create_rectangle( | |
x0, | |
y0, | |
x0 + size, | |
y0 + size, | |
outline="#222", | |
fill=c.player[0].color if c.player else "gray", | |
width=width, | |
) | |
if c.player: | |
u, v = c.player[0].pos | |
self.board_canvas.create_text( | |
x0 + size / 2, | |
y0 + size / 2, | |
text=c.player[0].name[:2].upper(), | |
font=( | |
"Sans", | |
10, | |
"normal {}".format("bold" if u == y and v == x else ""), | |
), | |
) | |
self.rank_canvas.delete("all") | |
ranking = sorted( | |
self.model.allplayer, | |
key=cmp_to_key(lambda a, b: len(b.occupied) - len(a.occupied)), | |
) | |
bonus = self.compute_bonus(ranking) | |
for i, p in enumerate(ranking): | |
x0 = padding | |
y0 = padding + size * i | |
self.rank_canvas.create_text( | |
x0 + size / 2, y0 + size / 2, text=str(len(p.occupied)) | |
) | |
x0 += size | |
self.rank_canvas.create_rectangle( | |
x0, y0, x0 + size, y0 + size, outline="#222", fill=p.color, width=width | |
) | |
self.rank_canvas.create_text( | |
x0 + size / 2, y0 + size / 2, text=p.name[:2].upper() | |
) | |
x0 += size * 3 | |
self.rank_canvas.create_text( | |
x0, y0 + size / 2, text="NT ${:,.1f}".format(450 * len(p.occupied)) | |
) | |
def animate(self, speed): | |
self.update() | |
self.after(speed, self.model.step) | |
def initUI(self): | |
self.master.title("Territory") | |
main_frame = Frame(self, relief=RAISED, borderwidth=1) | |
main_frame.pack(fill=BOTH, expand=True) | |
self.board_canvas = Canvas(main_frame) | |
self.board_canvas.pack(side=LEFT, fill=BOTH, expand=1) | |
self.rank_canvas = Canvas(main_frame) | |
self.rank_canvas.pack(side=LEFT, fill=BOTH, expand=1) | |
self.pack(fill=BOTH, expand=1) | |
start_button = Button(self, text="Start", command=self.model.step) | |
start_button.pack(side=RIGHT) | |
def main(): | |
random.seed() | |
pi0 = [ | |
("Anderson", (11, 11)), | |
("Colin", None), | |
("David", None), | |
("Dio", None), | |
("Dylan", None), | |
("Justin", None), | |
("Keeli", None), | |
("Mark", None), | |
("Paul", None), | |
("Sam", (0, 0)), | |
("Scott", None), | |
("Steve", None), | |
] | |
assert len(pi0) == len(bonus_setup) | |
pi1 = [ | |
("Anderson", (0, 0)), | |
("Colin", (1, 0)), | |
("David", (2, 0)), | |
("Dio", (3, 0)), | |
("Dylan", (4, 0)), | |
("Justin", (5, 0)), | |
("Keeli", (6, 0)), | |
("Mark", (7, 0)), | |
("Paul", (8, 0)), | |
("Sam", (9, 0)), | |
("Scott", (10, 0)), | |
("Steve", (11, 0)), | |
] | |
assert len(pi1) == len(bonus_setup) | |
pi2 = [ | |
("Anderson", (0, 0)), | |
("Colin", (1, 1)), | |
("David", (2, 2)), | |
("Dio", (3, 3)), | |
("Dylan", (4, 4)), | |
("Justin", (5, 5)), | |
("Keeli", (6, 6)), | |
("Mark", (7, 7)), | |
("Paul", (8, 8)), | |
("Sam", (9, 9)), | |
("Scott", (10, 10)), | |
("Steve", (11, 11)), | |
] | |
assert len(pi2) == len(bonus_setup) | |
root = Tk() | |
root.geometry("600x430") | |
model = Model(pi0) | |
game = Gui(model) | |
model.setup() | |
root.mainloop() | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment