Added the boardgame tic-tac-toe without winning conditions, written entirely in Python.
This commit is contained in:
parent
0590fa6ca1
commit
affcb739b4
28 changed files with 448 additions and 0 deletions
BIN
games/tic-tac-toe/.main.py.swp
Normal file
BIN
games/tic-tac-toe/.main.py.swp
Normal file
Binary file not shown.
91
games/tic-tac-toe/TODO
Normal file
91
games/tic-tac-toe/TODO
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
- background image
|
||||||
|
- subimages for game_area:s
|
||||||
|
- subimages for game markers (X or 0)
|
||||||
|
- rectangle collision on game_area:s
|
||||||
|
- redraw background then all game_area:s
|
||||||
|
- board_state: a hashtable where key is game_area_index
|
||||||
|
and value is either'x' 'o' or ' '
|
||||||
|
|
||||||
|
board contains nr_of_squares = 9
|
||||||
|
board contains array of squares with nr_of_squares elements
|
||||||
|
board contains x, y, width and height
|
||||||
|
board contains a frame
|
||||||
|
|
||||||
|
frame contains width and height
|
||||||
|
|
||||||
|
game_area contains.
|
||||||
|
game_area contains index.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
game init:
|
||||||
|
t
|
||||||
|
//frame is initialized with given width and height
|
||||||
|
//frame is initialized with color
|
||||||
|
//board is initialized with frame
|
||||||
|
board is initialized with given nr_of_squares
|
||||||
|
square_dimensions is initialized with x, y width and height
|
||||||
|
board is initialized with square_dimensions
|
||||||
|
board method init() is invoked
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
board:init(canva, nr_of_squares, square_dimensions):
|
||||||
|
img_sqr_x = load x image from harddisk
|
||||||
|
img_sqr_y = load y image from harddisk
|
||||||
|
|
||||||
|
squares = new array of squares with nr_of squares elements
|
||||||
|
|
||||||
|
for i in nr_of_squares:
|
||||||
|
square[i].init(square_dimensions, i, nr_of_squares)
|
||||||
|
|
||||||
|
|
||||||
|
state of all game_areas is set to ' '.
|
||||||
|
|
||||||
|
board:paint():
|
||||||
|
frame_paint()
|
||||||
|
squares_paint()
|
||||||
|
|
||||||
|
|
||||||
|
board:
|
||||||
|
ptr canva
|
||||||
|
img_sqr_x
|
||||||
|
img_sqr_o
|
||||||
|
//img_sqr_blank
|
||||||
|
|
||||||
|
//frame
|
||||||
|
|
||||||
|
array of squares
|
||||||
|
|
||||||
|
squares_paint():
|
||||||
|
for square in squares:
|
||||||
|
// May paint blank also
|
||||||
|
if square.state is x:
|
||||||
|
canva.paint(square.x, square.y, img_sqr_x)
|
||||||
|
elif square.state is o:
|
||||||
|
canva.paint(square.x, square.y, img_sqr_o)
|
||||||
|
|
||||||
|
square:
|
||||||
|
int x,y,width,height
|
||||||
|
char state
|
||||||
|
|
||||||
|
init(square_dimensions, index, nr_of_squares):
|
||||||
|
//calculate x and y based on width, height and index
|
||||||
|
//should really been done in squares class instead
|
||||||
|
self.x = width * (index % (sqrt(nr_of_squares)))
|
||||||
|
self.y = height * (index % (sqrt(nr_of_squares)))
|
||||||
|
state is ' '
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
all values of board_state is ' '.
|
||||||
|
array of game_area:s initialized with game_positions
|
||||||
|
according to the board
|
||||||
|
-game loop:
|
||||||
|
1. print background
|
||||||
|
2. f
|
||||||
|
|
||||||
|
--marked gamepositions when hovering them
|
||||||
|
|
BIN
games/tic-tac-toe/board.png
Normal file
BIN
games/tic-tac-toe/board.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 264 KiB |
18
games/tic-tac-toe/board.py
Normal file
18
games/tic-tac-toe/board.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
from rectangle import Rectangle
|
||||||
|
from gamerectangle import GameRectangle
|
||||||
|
from math import sqrt
|
||||||
|
#param: nr_of_squares, dimensions(could be a Rectangle)
|
||||||
|
#creates an array of gamerectangles within itself with correct
|
||||||
|
#positions from each other
|
||||||
|
|
||||||
|
class Board(object):
|
||||||
|
def __init__(self, nr_of_rectangles, dimensions):
|
||||||
|
self.game_rectangles = []
|
||||||
|
axis = sqrt(nr_of_rectangles)
|
||||||
|
width = dimensions.width
|
||||||
|
height = dimensions.height
|
||||||
|
for index in range(nr_of_rectangles):
|
||||||
|
x = width * (index % axis)
|
||||||
|
y = height * int(index / axis)
|
||||||
|
gr = GameRectangle(index, x, y, width, height)
|
||||||
|
self.game_rectangles.append(gr)
|
BIN
games/tic-tac-toe/board.pyc
Normal file
BIN
games/tic-tac-toe/board.pyc
Normal file
Binary file not shown.
BIN
games/tic-tac-toe/e.png
Normal file
BIN
games/tic-tac-toe/e.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
7
games/tic-tac-toe/gamerectangle.py
Normal file
7
games/tic-tac-toe/gamerectangle.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from rectangle import Rectangle
|
||||||
|
|
||||||
|
class GameRectangle(Rectangle):
|
||||||
|
def __init__(self, index, x, y, width, height):
|
||||||
|
self.index = index
|
||||||
|
self.state = ' '
|
||||||
|
Rectangle.__init__(self, x, y, width, height)
|
BIN
games/tic-tac-toe/gamerectangle.pyc
Normal file
BIN
games/tic-tac-toe/gamerectangle.pyc
Normal file
Binary file not shown.
23
games/tic-tac-toe/input.py
Normal file
23
games/tic-tac-toe/input.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#Input.py
|
||||||
|
|
||||||
|
#Variables: Key_Id[Int,String], KeyBehaviour[enum], Action[method]
|
||||||
|
#Methods: update() Param nothing,
|
||||||
|
#Behaviour
|
||||||
|
|
||||||
|
|
||||||
|
#in update. when called. takes the state of the key. compares with the behaviour.
|
||||||
|
#if fullfilled perform action.
|
||||||
|
|
||||||
|
|
||||||
|
#All Input instances have an id.
|
||||||
|
class Input(object):
|
||||||
|
id = -1
|
||||||
|
fun = None
|
||||||
|
def __init__(self, id, fun):
|
||||||
|
self.id = id
|
||||||
|
self.fun = fun
|
||||||
|
|
||||||
|
def notify(self):
|
||||||
|
pass
|
||||||
|
def update(self,state):
|
||||||
|
pass
|
BIN
games/tic-tac-toe/input.pyc
Normal file
BIN
games/tic-tac-toe/input.pyc
Normal file
Binary file not shown.
71
games/tic-tac-toe/inputmanager.py
Normal file
71
games/tic-tac-toe/inputmanager.py
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
#Input_Manager.py
|
||||||
|
#
|
||||||
|
|
||||||
|
from sys import exit
|
||||||
|
import pygame.event as event
|
||||||
|
import pygame.key as key
|
||||||
|
from pygame import QUIT,KEYDOWN,KEYUP,MOUSEBUTTONDOWN,MOUSEBUTTONUP
|
||||||
|
from keypress import KeyPress
|
||||||
|
from mousepress import MousePress
|
||||||
|
|
||||||
|
|
||||||
|
#inputlist: [Input]
|
||||||
|
#Input = {Key_Id, Key_Behaviour, Action}
|
||||||
|
#Key_Id: Id of a pygame key or a String matching the pygame key
|
||||||
|
#Key_Behaviour: OnPress OnRelease etc
|
||||||
|
#Action: A function to perform when the given input occurs.
|
||||||
|
|
||||||
|
#Key_Dictionary: A dictionary of Strings for the corresponding pygame key id:s
|
||||||
|
#To speed up input lookups a store a dictionary where each key corresponds to an input or a set of inputs
|
||||||
|
#for each update, lookup each key input in dict to get the affected inputs only
|
||||||
|
|
||||||
|
#Each Input subclass name must begin with Key
|
||||||
|
|
||||||
|
|
||||||
|
#Functions
|
||||||
|
#add: Add an input to the list of inputs
|
||||||
|
#remove: Remove an input from the list of inputs
|
||||||
|
#Process: For all inputs, check if any of them is occuring and trigger the corresponding action
|
||||||
|
|
||||||
|
|
||||||
|
#Defect: Press two keys at the same time and release them. Should some time give 2 KeyUp events but sometimes only gets one.
|
||||||
|
|
||||||
|
class InputManager(object):
|
||||||
|
#param: [Input]
|
||||||
|
#requires:Pygame is initialized
|
||||||
|
inputs = None
|
||||||
|
def __init__(self, inputs):
|
||||||
|
self.inputs = []
|
||||||
|
for i in inputs: #LAST UPDATED HERE!!!
|
||||||
|
if i[0] is "Key":
|
||||||
|
key_behaviour = "Key" + i[2]
|
||||||
|
#key_behaviour = key_behaviour + "." + key_behaviour
|
||||||
|
self.inputs.append(eval(key_behaviour)(i[1],i[3]))
|
||||||
|
elif i[0] is "Mouse":
|
||||||
|
mouse_behaviour = "Mouse" + i[2]
|
||||||
|
#mouse_behaviour = mouse_behaviour + "." + mouse_behaviour
|
||||||
|
self.inputs.append(eval(mouse_behaviour)(i[1],i[3]))
|
||||||
|
#desc: Look up the refreshed current input state and call the affected methods
|
||||||
|
def update(self):
|
||||||
|
for e in event.get():
|
||||||
|
if e.type == QUIT: exit()
|
||||||
|
if e.type is KEYDOWN:
|
||||||
|
for i in self.inputs:
|
||||||
|
if i.id == e.key:
|
||||||
|
i.update("Down")
|
||||||
|
if e.type is KEYUP:
|
||||||
|
for i in self.inputs:
|
||||||
|
if i.id == e.key:
|
||||||
|
i.update("Up")
|
||||||
|
if e.type is MOUSEBUTTONDOWN:
|
||||||
|
for i in self.inputs:
|
||||||
|
if i.id == e.button:
|
||||||
|
i.update("Down")
|
||||||
|
if e.type is MOUSEBUTTONUP:
|
||||||
|
for i in self.inputs:
|
||||||
|
if i.id == e.button:
|
||||||
|
i.update("Up")
|
||||||
|
|
||||||
|
for i in self.inputs:
|
||||||
|
i.notify()
|
||||||
|
#chec for keyUpdates
|
BIN
games/tic-tac-toe/inputmanager.pyc
Normal file
BIN
games/tic-tac-toe/inputmanager.pyc
Normal file
Binary file not shown.
29
games/tic-tac-toe/keypress.py
Normal file
29
games/tic-tac-toe/keypress.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#KeyPress.py
|
||||||
|
|
||||||
|
#Should call method if a key was pressed
|
||||||
|
#Variales: isPressed, actionPerformed
|
||||||
|
#To make sure that the key only get's pressed once when holding a button down, Two variables are needed.
|
||||||
|
#isPressed: If not pressed, call method on next keypress
|
||||||
|
#actionPerformed: If method was called and key is still down, do not call method no more
|
||||||
|
#Input instances will be allocated before gameplay, therefore: isPressed and actionPerformed will be initialized false.
|
||||||
|
|
||||||
|
#methods: init, update
|
||||||
|
#update: Will be called when there is a change in this actual keys state. With respect to the input class behaviour,
|
||||||
|
#update will evaluate if it's corresponding method should be called or not
|
||||||
|
#param: state tells if the key is down or up.
|
||||||
|
|
||||||
|
|
||||||
|
#Four possible preconditions: is not pressed and key is up. -> Set isPressed to false.
|
||||||
|
# is not pressed and key is down. ->
|
||||||
|
# is pressed and key is up.
|
||||||
|
# is pressed and key is down.
|
||||||
|
|
||||||
|
from input import Input
|
||||||
|
|
||||||
|
class KeyPress(Input):
|
||||||
|
def __init__(self, id, function):
|
||||||
|
Input.__init__(self, id, function)
|
||||||
|
|
||||||
|
def update(self,state):
|
||||||
|
if state == "Down":
|
||||||
|
self.fun()
|
BIN
games/tic-tac-toe/keypress.pyc
Normal file
BIN
games/tic-tac-toe/keypress.pyc
Normal file
Binary file not shown.
47
games/tic-tac-toe/main.py
Normal file
47
games/tic-tac-toe/main.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
from sys import exit
|
||||||
|
#from pygame.image import load
|
||||||
|
from pygame.display import set_mode, flip
|
||||||
|
from pygame import init
|
||||||
|
import pygame.event as event
|
||||||
|
from pygame.mouse import get_pos
|
||||||
|
from pygame import QUIT,K_UP
|
||||||
|
from inputmanager import InputManager
|
||||||
|
from tictactoeboard import TicTacToeBoard
|
||||||
|
from point import Point
|
||||||
|
|
||||||
|
#inputmanager = None
|
||||||
|
#screen = None
|
||||||
|
#board = None
|
||||||
|
|
||||||
|
def main():
|
||||||
|
global inputmanager,screen,board
|
||||||
|
#init graphics
|
||||||
|
init()
|
||||||
|
size = (150,150)
|
||||||
|
screen = set_mode(size)
|
||||||
|
|
||||||
|
#init game data
|
||||||
|
nr_of_rectangles = 9
|
||||||
|
board = TicTacToeBoard(nr_of_rectangles)
|
||||||
|
#board.paint(screen)
|
||||||
|
|
||||||
|
#init input
|
||||||
|
inputmanager = InputManager([
|
||||||
|
("Mouse", 1, "Press", (lambda: board.make_turn(Point(get_pos()[0],get_pos()[1])))),
|
||||||
|
("Key", K_UP, "Press", (lambda: print("Hello Keyboard!"))),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
loop()
|
||||||
|
|
||||||
|
def loop():
|
||||||
|
global inputmanager,screen,board
|
||||||
|
while True:
|
||||||
|
inputmanager.update()
|
||||||
|
board.paint(screen)
|
||||||
|
flip()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
9
games/tic-tac-toe/mousepress.py
Normal file
9
games/tic-tac-toe/mousepress.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
from input import Input
|
||||||
|
|
||||||
|
class MousePress(Input):
|
||||||
|
def __init__(self, id, function):
|
||||||
|
Input.__init__(self, id, function)
|
||||||
|
|
||||||
|
def update(self,state):
|
||||||
|
if state == "Down":
|
||||||
|
self.fun()
|
BIN
games/tic-tac-toe/mousepress.pyc
Normal file
BIN
games/tic-tac-toe/mousepress.pyc
Normal file
Binary file not shown.
BIN
games/tic-tac-toe/o.png
Normal file
BIN
games/tic-tac-toe/o.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.5 KiB |
12
games/tic-tac-toe/point.py
Normal file
12
games/tic-tac-toe/point.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
from rectangle import Rectangle
|
||||||
|
|
||||||
|
class Point(object):
|
||||||
|
def __init__(self, x, y):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
|
||||||
|
def inside(self, rectangle):
|
||||||
|
return ((self.x >= rectangle.x)
|
||||||
|
and (self.x <= (rectangle.x + rectangle.width))
|
||||||
|
and (self.y >= rectangle.y)
|
||||||
|
and (self.y <= rectangle.y + rectangle.height))
|
BIN
games/tic-tac-toe/point.pyc
Normal file
BIN
games/tic-tac-toe/point.pyc
Normal file
Binary file not shown.
8
games/tic-tac-toe/rectangle.py
Normal file
8
games/tic-tac-toe/rectangle.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
class Rectangle(object):
|
||||||
|
def __init__(self, x, y, width, height):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
|
BIN
games/tic-tac-toe/rectangle.pyc
Normal file
BIN
games/tic-tac-toe/rectangle.pyc
Normal file
Binary file not shown.
32
games/tic-tac-toe/test_board.py
Normal file
32
games/tic-tac-toe/test_board.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import unittest
|
||||||
|
from board import Board
|
||||||
|
from rectangle import Rectangle
|
||||||
|
|
||||||
|
class TestBoard(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
nr_of_rectangles = 4
|
||||||
|
dimensions = Rectangle(0, 0, 25, 35)
|
||||||
|
self.b = Board(nr_of_rectangles, dimensions)
|
||||||
|
|
||||||
|
def test_rectangle_placemenets(self):
|
||||||
|
#rectangle1: 0,0,25,35
|
||||||
|
#rectangle2: 26,0,25,35
|
||||||
|
#rectangle3: 0,36,25,35
|
||||||
|
#rectangle4: 26,36,25,35
|
||||||
|
|
||||||
|
r0 = self.b.game_rectangles[0]
|
||||||
|
r1 = self.b.game_rectangles[1]
|
||||||
|
r2 = self.b.game_rectangles[2]
|
||||||
|
r3 = self.b.game_rectangles[3]
|
||||||
|
self.assertEqual(r0.x, 0)
|
||||||
|
self.assertEqual(r0.y, 0)
|
||||||
|
self.assertEqual(r1.x, 25)
|
||||||
|
self.assertEqual(r1.y, 0)
|
||||||
|
self.assertEqual(r2.x, 0)
|
||||||
|
self.assertEqual(r2.y, 35)
|
||||||
|
self.assertEqual(r3.x, 25)
|
||||||
|
self.assertEqual(r3.y, 35)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
23
games/tic-tac-toe/test_gamerectangle.py
Normal file
23
games/tic-tac-toe/test_gamerectangle.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import unittest
|
||||||
|
from gamerectangle import GameRectangle
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TestGameRectangle(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.index = 0
|
||||||
|
self.x = 1
|
||||||
|
self.y = 2
|
||||||
|
self.width = 34
|
||||||
|
self.height = 0.23
|
||||||
|
self.gr = GameRectangle(self.index, self.x, self.y, self.width, self.height)
|
||||||
|
def test_attributes(self):
|
||||||
|
self.assertEqual(self.index, self.gr.index)
|
||||||
|
self.assertEqual(self.gr.state, ' ')
|
||||||
|
self.assertEqual(self.x, self.gr.x)
|
||||||
|
self.assertEqual(self.y, self.gr.y)
|
||||||
|
self.assertEqual(self.width, self.gr.width)
|
||||||
|
self.assertEqual(self.height, self.gr.height)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
27
games/tic-tac-toe/test_rectangle.py
Normal file
27
games/tic-tac-toe/test_rectangle.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import unittest
|
||||||
|
from rectangle import Rectangle
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
x = 1
|
||||||
|
y = 2
|
||||||
|
width = 34
|
||||||
|
height = 0.23
|
||||||
|
r = Rectangle(x, y, width, height)
|
||||||
|
|
||||||
|
class TestRectangle(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.x = 1
|
||||||
|
self.y = 2
|
||||||
|
self.width = 34
|
||||||
|
self.height = 0.23
|
||||||
|
self.r = Rectangle(self.x, self.y, self.width, self.height)
|
||||||
|
def test_attributes(self):
|
||||||
|
self.assertEqual(self.x, self.r.x)
|
||||||
|
self.assertEqual(self.y, self.r.y)
|
||||||
|
self.assertEqual(self.r.width, self.r.width)
|
||||||
|
self.assertEqual(self.r.height, self.r.height)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
51
games/tic-tac-toe/tictactoeboard.py
Normal file
51
games/tic-tac-toe/tictactoeboard.py
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
from board import Board
|
||||||
|
from rectangle import Rectangle
|
||||||
|
from point import Point
|
||||||
|
from pygame.image import load
|
||||||
|
from pygame.rect import Rect
|
||||||
|
from pygame import Surface
|
||||||
|
|
||||||
|
#inherits Board.
|
||||||
|
#Used for displaying the board on the screen and interact with it
|
||||||
|
#thus images are needed for the various rectangles
|
||||||
|
#as well as a paint function
|
||||||
|
#Should be updated after every valid player move
|
||||||
|
|
||||||
|
class TicTacToeBoard(Board):
|
||||||
|
def __init__(self, nr_of_rectangles):
|
||||||
|
self.image_e = load("e.png")
|
||||||
|
self.image_x = load("x.png")
|
||||||
|
self.image_o = load("o.png") #TODO add the o image
|
||||||
|
dimensions = Rectangle(0, 0, self.image_x.get_width(),
|
||||||
|
self.image_x.get_height())
|
||||||
|
self.players_turn = 0
|
||||||
|
Board.__init__(self, nr_of_rectangles, dimensions)
|
||||||
|
|
||||||
|
def paint(self, table_image):
|
||||||
|
for game_rectangle in self.game_rectangles:
|
||||||
|
x = game_rectangle.x
|
||||||
|
y = game_rectangle.y
|
||||||
|
w = game_rectangle.width
|
||||||
|
h = game_rectangle.height
|
||||||
|
|
||||||
|
image = None
|
||||||
|
if game_rectangle.state == ' ':
|
||||||
|
image = self.image_e
|
||||||
|
elif game_rectangle.state == 'x':
|
||||||
|
image = self.image_x
|
||||||
|
elif game_rectangle.state == 'o':
|
||||||
|
image = self.image_o
|
||||||
|
print(game_rectangle.state)
|
||||||
|
table_image.blit(image, Rect(x, y, w, h))
|
||||||
|
|
||||||
|
def make_turn(self, mouse_point):
|
||||||
|
for game_rectangle in self.game_rectangles:
|
||||||
|
if (mouse_point.inside(game_rectangle) and
|
||||||
|
game_rectangle.state == ' '):
|
||||||
|
if self.players_turn == 0:
|
||||||
|
game_rectangle.state = 'x'
|
||||||
|
elif self.players_turn == 1:
|
||||||
|
game_rectangle.state = 'o'
|
||||||
|
self.players_turn = (self.players_turn + 1) % 2
|
||||||
|
|
||||||
|
|
BIN
games/tic-tac-toe/tictactoeboard.pyc
Normal file
BIN
games/tic-tac-toe/tictactoeboard.pyc
Normal file
Binary file not shown.
BIN
games/tic-tac-toe/x.png
Normal file
BIN
games/tic-tac-toe/x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.5 KiB |
Reference in a new issue