LoginSignup
6
8

More than 5 years have passed since last update.

Python で Hex map を作ってみた

Last updated at Posted at 2017-07-11

学校での学習の一環で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

コード

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

6
8
7

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
8