Python で Hex map を作ってみた

学校での学習の一環でpythonライブラリ「pygame」でゲームを作っています。シミュレーションゲームなのでよくあるヘックスマップを作ろうとしましたが、使えるコードがなかなかヒットしない。結局自分で一から作ることになりました。…だったのですが稚拙なコードをQiitaユーザーのshiracamus様に書き直していただきました！ありがとうございます。

参考にしたサイト
http://www.redblobgames.com/grids/hexagons/
http://qiita.com/kapipara-games/items/7978a7b51a47df8ace38

環境
- Windows10 VirtualBox Ubuntu16.04
- python3.5 pygame1.9.3

##コード

```py:my_hexmap.py
#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import pygame
from pygame.locals import *

# colors (Red, Green, Blue)
BLACK = (0, 0, 0)
ORANGE = (255, 120, 0)
YELLOW = (255, 255, 0)
CYAN = (0, 255, 255)


class Hexagon:
    """
    Hexagon index/pixel coordinate converter
    There is no limit to the number of hexagon.

       x: 0/4 1/4 3/4 4/4
      y:    |  |   |  |
    0/2--   v0 _____ v1
              /     \
    1/2--    /       \
          v5 \       / v2
    2/2--     \_____/
             v4       v3
    """

    VERTEXES = (1, 0), (3, 0), (4, 1), (3, 2), (1, 2), (0, 1)

    def __init__(self, size):
        self.SIZE = size
        self.HALF = size * 1 // 2
        self.QUARTER = size * 1 // 4
        self.WIDTH = size * 3 // 4
        self.HEIGHT = size

    def index_at(self, pixel_x, pixel_y):
        """get the index coordinae at the pixel coordinate"""
        x = pixel_x // self.WIDTH
        x0 = pixel_x % self.WIDTH
        if x0 < self.QUARTER:  # overlap area
            y0 = (pixel_y - (x % 2) * self.HALF) % self.HEIGHT
            if y0 >= self.HALF:  # lower half
                y0 = self.SIZE - y0
            is_leftarea = x0 < self.QUARTER - y0 * self.QUARTER // self.HALF
            if is_leftarea:
                x -= 1
        y = (pixel_y - (x % 2) * self.HALF) // self.HEIGHT
        return x, y

    def origin_of(self, x, y):
        return x * self.WIDTH, y * self.HEIGHT + (x % 2) * self.HALF

    def vertexes_of(self, x, y):
        """get vertex coodinates of the index coordinate"""
        origin_x, origin_y = self.origin_of(x, y)
        return [(origin_x + vx * self.QUARTER, origin_y + vy * self.HALF)
                for vx, vy in self.VERTEXES]


class HexMap:
    """
    Hexagon map
     ___ x,y ___
    /0,0\___/2,0\
    \___/1,0\___/
    /0,1\___/2,1\
    \___/1,1\___/
    /0,2\___/2,2\
    \___/   \___/
    """

    def __init__(self, columns, rows, hexagon_size):
        self.COLUMNS = columns
        self.ROWS = rows
        self.hexagon = Hexagon(hexagon_size)
        width, height = self.hexagon.origin_of(self.COLUMNS, self.ROWS)
        width += self.hexagon.QUARTER
        self.size = width + 1, height + 1
        self.clear_clicked()

    def clear_clicked(self):
        self.clicked = [[False] * self.ROWS for x in range(self.COLUMNS)]

    def click_at(self, pixel_x, pixel_y):
        x, y = self.hexagon.index_at(pixel_x, pixel_y)
        if 0 <= x < self.COLUMNS and 0 <= y < self.ROWS:
            self.clicked[x][y] = not self.clicked[x][y]

    def is_clicked_on(self, x, y):
        return (0 <= x < self.COLUMNS and 0 <= y < self.ROWS and
                self.clicked[x][y])

    def hexagons(self):
        for x in range(self.COLUMNS):
            for y in range(self.ROWS - (x % 2)):
                yield self.hexagon, x, y


class HexCanvas:
    LINE_WIDTH = 1
    CURSOR_SIZE = 5

    def __init__(self, hexmap):
        self.hexmap = hexmap
        self.canvas = pygame.display.set_mode(hexmap.size)
        self.font = pygame.font.SysFont(None, 17)
        self.cursor = None

    def cursor_at(self, pixel_x, pixel_y):
        self.cursor = pixel_x, pixel_y

    def draw(self):
        canvas = self.canvas
        canvas.fill(BLACK)
        hexmap = self.hexmap
        for hexagon, x, y in hexmap.hexagons():
            vertexes = hexagon.vertexes_of(x, y)
            # 六角形塗り潰し
            color = ORANGE if hexmap.is_clicked_on(x, y) else YELLOW
            pygame.draw.polygon(canvas, color, vertexes)
            # 六角形枠線描画
            for v1, v2 in zip(vertexes, vertexes[1:] + vertexes[:1]):
                pygame.draw.line(canvas, BLACK, v1, v2, self.LINE_WIDTH)
            # 座標タグ描画
            tag = "{0}, {1}".format(x, y)
            text = self.font.render(tag, True, CYAN)
            origin_x, origin_y = hexagon.origin_of(x, y)
            text_area = text.get_rect(center=(origin_x + hexagon.HALF,
                                                  origin_y + hexagon.HALF))
            canvas.blit(text, text_area)
        # カーソル描画
        if self.cursor:
            pygame.draw.circle(canvas, BLACK, self.cursor, self.CURSOR_SIZE, 0)


def main():
    pygame.init()
    FPSCLOCK = pygame.time.Clock()

    hexmap = HexMap(columns=20, rows=15, hexagon_size=50)
    canvas = HexCanvas(hexmap)

    while True:
        for event in pygame.event.get():
            if event.type == QUIT or event.type == KEYDOWN and event.key == K_q:
                pygame.quit()
                return
            if event.type == MOUSEMOTION:
                x, y = pygame.mouse.get_pos()
                canvas.cursor_at(x, y)
            elif event.type == MOUSEBUTTONDOWN:
                x, y = pygame.mouse.get_pos()
                hexmap.click_at(x, y)

        canvas.draw()
        pygame.display.update()
        FPSCLOCK.tick(30)

if __name__ == '__main__':
    main()
```

##解説
- 

以下をubuntuで実行した画像
![hexview.png](https://qiita-image-store.s3.amazonaws.com/0/149523/c1bb9b69-418b-9ad7-c74d-6478db8ab945.png)